react-native-animation-scenario
v1.4.3
Published
Declarative animation scenario hook for React Native
Maintainers
Readme
🎬 react-native-animation-scenario
A lightweight, declarative animation timeline hook for React Native — perfect for scenarios like onboarding, tutorials, interactive demos, and more. Easily control complex UI animations step-by-step or automatically.
Works with React Native and Expo.
✨ Features
- ✅ Declarative animation timeline with step-by-step control
- 🧠 Supports
set,move,delay,parallel,callback,vibrate,hold,label,goto,resume,use,ifJump,ifThenandusesteps - 🎛 Works in both
"auto"and"manual"step modes - 🔁 Loopable scenarios
- 📦 Minimal dependencies – just React Native + Animated
- 🧩 Drop-in
TimelineViewfor debug or dev overlay - 🔐 Safe callback and ref validation
⚡ Try it live
🔥 What's New in 1.4.3
- ✅ documentation added.
🔥 What's New in 1.4.2 Patch Release
- 🛠 Fixed an issue where the `stop() step didn’t always correctly interrupt the scenario execution.
- ✅ Improved overall code structure and performance with internal refactoring.
🔥 What's New in 1.4.0
- ✅
ifThen,ifElse,ifEnd: New block-style conditional logic, familiar to developers. Example:
ifThen(() => score.current > 5),
move("x", 100, 500),
ifElse(),
move("x", -100, 500),
ifEnd()- ✅ Nested conditionals are supported and validated at compile time.
- ✅ Improved compiler validation to detect malformed blocks or unclosed conditionals.
- ✅ Relative values in
move()using helper functions likeinc(value)ordec(value). Example:
move("x", inc(50), 500)- ✅ Arguments in
callback()steps now supported. - ✅ Jump to label in
nextStep(label): You can resume or redirect flow from UI or interaction code.
🔥 What's New in 1.3.0
- ✅
resumestep: jump back to the point after the lastgoto()call - ✅
setstep: set a value directly or from a function - ✅
stopstep: stop and reset the animation - ✅
ifJump(condition, labelTrue, labelFalse?)step: jump based on condition - 🛡 Label validation for
gotoandifJumpsteps now included in scenario compilation - ⚙️ Cleaned and centralized all validation logic in
compileScenario()
🔥 What's New in 1.2.0
- ✅
goto("label")step: jump to a specific label in your scenario - ✅
label("name")step: define named labels - ✅ Nested reusable blocks with
use("blockName") - ✅ Full
reset()andstop()logic - ✅
TimelineViewfor visual debug - ✅
useScreenLifecycle(onFocus, onBlur)helper
📦 Installation
npm install react-native-animation-scenario📦 Exports
useAnimationScenario(); // Main hook
defineScenario([]); // Safely define your scenario
move(), delay(), callback(), ... // Step helpers
useScreenLifecycle(); // Hook for screen focus lifecycle🛠 Usage
import {
useAnimationScenario,
defineScenario,
move,
delay,
parallel,
callback,
hold,
} from "react-native-animation-scenario";
const scenario = defineScenario([
move("opacity", 1, 500),
delay(300),
parallel([
move("scale", 1.1, 500),
move("translateY", 50, 500),
]),
hold("waitForTap"),
callback("onFinish"),
]);
const MyComponent = () => {
const {
refs,
start,
reset,
nextStep,
TimelineView,
} = useAnimationScenario({
scenario,
initialValues: {
opacity: 0,
scale: 1,
translateY: 0,
},
callbacks: {
onFinish: () => console.log("Done!"),
},
});
useEffect(() => { start(); }, []);
return (
<Animated.View style={{
opacity: refs.opacity,
transform: [
{ scale: refs.scale },
{ translateY: refs.translateY }
]
}}>
<Text>Hello Animation</Text>
<TimelineView />
</Animated.View>
);
};🔧 Step Types
| Type | Description | Version |
|------------|----------------------------------------------|---------|
| move | Animate a ref value to a target | 1.0 |
| delay | Pause for a given duration | 1.0 |
| parallel | Animate multiple values simultaneously | 1.0 |
| callback | Run external logic (sync or async) | 1.0 |
| vibrate | Trigger haptic feedback | 1.0 |
| hold | Pause animation until nextStep() is called | 1.0 |
| label | Mark jump targets | 1.2 |
| goto | Jump to a label | 1.2 |
| use | Insert a block | 1.2 |
| set | Set a value directly or from a function | 1.3 |
| stop | Stop and reset the animation | 1.3 |
| resume | Continue from the point after last goto() | 1.3 |
| ifJump | Conditionally jump to a label | 1.3 |
| ifThen | Block-style conditional logic | 1.4 |
🧠 Modes
- mode: "auto" – steps play sequentially
- mode: "manual" – steps only run via nextStep()
- loop: true – repeat the scenario forever
🧩 Reusable Blocks
const blocks = {
bounce: defineScenario([
move("y", -30, 200),
move("y", 0, 200),
]),
};
const scenario = defineScenario([
callback("showStart"),
use("bounce"),
delay(500),
use("bounce"),
]);🤖 Conditional Branching with ifJump()
Use ifJump() to dynamically choose the next step at runtime.
const MyComponent = () => {
const directionRef = useRef(1);
const scenario = defineScenario([
hold(), // Wait for user input before branching
ifJump(() => directionRef.current === 1, "moveRight", "moveLeft"),
label("moveRight"),
move("x", 200, 500),
goto("resumePoint"),
label("moveLeft"),
move("x", 0, 500),
label("resumePoint"),
]);
const { refs, start, nextStep } = useAnimationScenario({
scenario,
initialValues: { x: 100 },
});
useEffect(() => { start(); }, []);
return (
<View>
<Animated.View
style={{
width: 50,
height: 50,
backgroundColor: "blue",
transform: [{ translateX: refs.x }],
marginBottom: 20,
}}
/>
<Text>Choose Direction:</Text>
<View style={{ flexDirection: "row", gap: 10 }}>
<Button title="Left" onPress={() => {
directionRef.current = 0;
nextStep();
}} />
<Button title="Right" onPress={() => {
directionRef.current = 1;
nextStep();
}} />
</View>
</View>
);
};🔁 ifJump() using a named callback
You can define reusable branching logic in callbacks:
const scenario = defineScenario([
hold(),
ifJump("checkDirection", "goRight", "goLeft"),
label("goRight"),
move("x", 100, 300),
goto("end"),
label("goLeft"),
move("x", -100, 300),
label("end"),
]);
const { refs, nextStep, start } = useAnimationScenario({
scenario,
initialValues: { x: 0 },
callbacks: {
checkDirection: () => Math.random() > 0.5,
},
});🧪 Advanced Flow Example: set, goto, resume, and stop
This scenario demonstrates how to use the new set, goto, resume, and stop steps for conditional or non-linear animation flows.
import { useAnimationScenario,defineScenario, label, vibrate, move, goto, stop, set, resume } from "react-native-animation-scenario";
const scenario = defineScenario([
label("start"),
vibrate(),
move("x", 100, 2000),
goto("reverse"), // Jump to reverse path
move("x", 200, 500, "after-resume"), // Will resume here after `resume`
stop(), // Stops the flow here
label("reverse"),
set("x", 0), // Instantly reset position
move("x", -100, 500),
resume(),
]);
const MyComponent = () => {
const {refs,start,TimelineView,} = useAnimationScenario({
scenario,
initialValues: { x: 0, direction: 0 },
callbacks: {
checkDirection: () => {
// Example: dynamically decide to reverse
if (Math.random() > 0.5) return goto("reverse");
},
},
});
return (
<Animated.View style={{ transform: [{ translateX: refs.x }] }} >
<Button title="Start" onPress={start} />
<TimelineView />
</Animated.View>
);
};🧪 Timeline Debug
Add the TimelineView to display step progress:
<TimelineView /> <!-- Optional -->Great for development or visual debugging of onboarding flows.
🛡 Safeguards
- 🚫 Throws error if an initialValue or callback is missing
- 🔒 All animations use Animated.Value and native drivers by default
🧩 Optional: Screen Lifecycle Hook
This helper hook requires @react-navigation/native and should be imported separately:
import { useScreenLifecycle } from 'react-native-animation-scenario/src/useScreenLifecycle';It allows you to trigger logic when the screen gains or loses focus.
📄 License
MIT – Created by Cyril Adam
📦 CHANGELOG for v1.4.2 and v1.4.1
🔧 Fixes
- Fixed a bug where the
stop()step didn't correctly interrupt scenario execution in all cases. - Improved internal handling of the
shouldStopflag to prevent race conditions and ensure proper reset behavior. reset()now explicitly clearsholdResolverandcallingStepIndexRef, making the scenario restart more predictable and clean.- Minor log improvements under debug mode.
💡 Internal Improvements
- Factored out reusable logic into
evalStepValue()andevalStepCondition()to unify async/sync step evaluation acrossset,ifThen, andcallbacksteps. - Jump logic now centralized via
jumpTo()utility, ensuring consistent block traversal acrossifThen,ifElse, andifEnd. - Internal state handling made more robust for future scalability and debugging ease.
- Scenario validation (
compileScenario) now checksparallelcontent blocks.
📜 CHANGELOG for v1.4.0
✨ Added
ifThen(condition),ifElse(),ifEnd()block structure: familiar conditional logic using nested blocks.nextStep(label): allows you to jump to a specific label manually, even after ahold()step.move(...)now supports relative values usinginc(x)anddec(x).- Extended support for
callback()to accept parameters, including dynamic values. - Scenario validation (
compileScenario) now checks for properly closedifThen/ifElse/ifEndblocks. - Modular refactoring for better extensibility.
🐞 Fixed
- Ensured
ifJump()does not rely on refs directly fromdefineScenario(). - Improved safety and label resolution for nested conditional logic.
- Fixed corner cases in
hold()+ jump logic fornextStep().
🧪 Testing
- Jest tests added for malformed conditional structures (
ifThenwithoutifEnd, duplicateifElse, etc.) - Unit tests extended to cover relative moves.
📜 CHANGELOG for v1.3.1
🐞 Fixed
- Move useScreenLifecycle to a separate export to avoid dependency issues on Snack
📜 CHANGELOG for v1.3.0
✨ Added
set(target, value): set a ref value directly, from a function, or from an async callbackstop(): terminates the current animation sequence cleanlyresume(): continues execution from the step after the lastgoto()jumpifJump(condition, labelTrue, labelFalse?): conditional branching based on a sync or async function or a callback name- Scenario validation now checks label existence for
gotoandifJump - Improved label jump tracking with
callingStepIndexReffor `resume()
🐞 Fixed
- Ensured
nextStep()respects theshouldStopflag - Cleaned up validation logic (moved to
compileScenario) - Deduplicated validation errors for clearer debug output
🧪 Testing
- New unit test coverage for scenario compilation including nested `use() blocks and label resolution.
- Some validation moved from runtime to compile-time, simplifying runtime hook logic.
📜 CHANGELOG for v1.2.0
✨ Added
goto("label")step to jump within the scenariolabel("name")step to declare jump targets- Support for
use("blockName")reusable block structure __sourceBlockannotation for debugging block origins- Visual timeline label display (
TimelineView) - Full
reset()support restores animated values stop()now halts looping scenarios safelyuseScreenLifecycle(onFocus, onBlur)hook
🐞 Fixed
- Scenario would continue running after screen unmount (fixed with
shouldStop) reset()didn't clear active animation state properly- Label detection and step validation improved
🧪 Testing
- Added Jest tests and modular
compileScenariosystem - Internal compilation now flattens nested
use()blocks
🤝 Contributing
- PRs welcome! Especially for:
- TypeScript types
- New step types (e.g. spring)
- Helper tooling
