@belkajo/motion-engine
v0.1.5
Published
Tiny stateful animation sequence engine on top of framer-motion.
Maintainers
Readme
Motion Engine (Scene + Sequence)
A tiny scene-based animation engine built on top of framer-motion. Designed for multi-step UI animations where each UI element evolves through states and coordinated transitions.
Status: Experimental — API may change.
✨ Features
- Scene builder (
Scene) for structured multi-step animations. - Incremental variants: later steps extend previous animation states.
- State extraction per step with
buildStatesFor(). - Sequence controller (
useSequence) automatically advances when all animated elements finish. - Animated wrapper (
AnimatedElement) integrates sequencing with framer-motion.
📦 Installation
In a pnpm monorepo:
pnpm add @belkajo/motion-engine --filter <your-app>🧱 Basic Concepts
Scene
A Scene consists of numbered animation steps:
import { Scene } from "@belkajo/motion-engine";
const scene = new Scene()
.addStep({
form: {
variant: { opacity: 0, y: -40 },
state: {}
}
})
.addStep({
form: {
variant: { opacity: 1, y: 0 },
state: {}
}
});Building variants for an element
const variants = scene.buildVariantsFor("form");Produces merged variants:
{
0: { opacity: 0, y: -40 },
1: { opacity: 1, y: 0 }
}Building states
const states = scene.buildStatesFor("form");🔁 Using useSequence
import { useSequence } from "@belkajo/motion-engine";
const { currentStep, onStart, onDone, setCurrentStep } = useSequence(scene);currentStep— active animation stepsetCurrentStep(n)— manually jump to a steponStart(id)— mark an element as animatingonDone(id)— mark animation complete and advance step when all finished
🔄 Resetting and restarting a sequence (important)
Give time to finish the animations after reset before starting the animation scenes.
🎬 AnimatedElement
Use instead of motion.div to integrate sequencing:
<AnimatedElement
variants={variants}
step={currentStep}
onStart={onStart}
onDone={onDone}
>
<YourComponent />
</AnimatedElement>Note on stability
AnimatedElement generates a stable internal id (via useMemo) which is used by useSequence
to track running animations. This is intentional.
Avoid conditionally changing or remounting individual AnimatedElements unless you explicitly
want to cancel their animations.
🧪 Example Putting It All Together
const scene = new Scene()
.addStep({ form: { variant: { opacity: 0 }, state: {} } })
.addStep({ form: { variant: { opacity: 1 }, state: {} } });
const variants = scene.buildVariantsFor("form");
const { currentStep, onStart, onDone } = useSequence(scene);
return (
<AnimatedElement
variants={variants}
step={currentStep}
onStart={onStart}
onDone={onDone}
>
<div>Animated Form</div>
</AnimatedElement>
);📘 Types
interface StepConfig<TState = unknown> {
variant: Variant;
state: TState;
}License
MIT
