Example: Staggered Animations
This plugin applies staggered entrance animations to every child inside a selected frame. Each child animates in sequence with a configurable delay, creating a cascading reveal effect.
What it does
- User selects a frame that contains child elements (cards, text blocks, images, etc.)
- The plugin reads all children and sorts them by render order
- Each child gets a
whileInViewanimation with an incrementing delay - When the frame scrolls into view, children animate in one by one
Full source
import { definePlugin } from '@revyme/plugin-sdk';
export default definePlugin({
name: 'Stagger Animation',
icon: 'layers',
description: 'Apply staggered entrance animations to children of a frame',
submitText: 'Apply Animation',
controls: {
opacity: {
type: 'number',
label: 'Start Opacity',
min: 0,
max: 1,
step: 0.1,
default: 0,
},
offsetY: {
type: 'number',
label: 'Offset Y',
min: -200,
max: 200,
step: 1,
default: 20,
},
scale: {
type: 'number',
label: 'Start Scale',
min: 0,
max: 2,
step: 0.05,
default: 0.95,
},
playOnce: {
type: 'toggle',
label: 'Play Once',
default: true,
},
threshold: {
type: 'number',
label: 'Threshold',
min: 0,
max: 1,
step: 0.1,
default: 0.3,
},
easeType: {
type: 'select',
label: 'Transition',
options: [
{ label: 'Spring', value: 'spring' },
{ label: 'Ease Out', value: 'easeOut' },
{ label: 'Ease In Out', value: 'easeInOut' },
{ label: 'Linear', value: 'linear' },
],
default: 'spring',
},
stiffness: {
type: 'number',
label: 'Stiffness',
min: 0,
max: 1000,
step: 10,
default: 280,
},
damping: {
type: 'number',
label: 'Damping',
min: 0,
max: 100,
step: 1,
default: 55,
},
delayGap: {
type: 'number',
label: 'Delay Gap',
min: 0,
max: 5,
step: 0.05,
default: 0.1,
},
},
run(values, sdk) {
const frame = sdk.nodes.getSelected()[0];
if (!frame) return;
const children = sdk.nodes.getChildren(frame.id);
if (children.length === 0) return;
// Sort children by their render order
const sorted = [...children].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0)
);
// Build transition config based on ease type
const baseTransition =
values.easeType === 'spring'
? {
type: 'spring',
stiffness: values.stiffness,
damping: values.damping,
mass: 1,
}
: {
type: 'tween',
duration: 0.5,
ease: values.easeType,
};
// Apply animation to each child with incrementing delay
sorted.forEach((child, i) => {
sdk.nodes.update(child.id, {
styles: {
...child.styles,
whileInView: {
initial: {
opacity: values.opacity,
y: values.offsetY,
scale: values.scale,
},
viewport: {
once: values.playOnce,
amount: values.threshold,
},
transition: {
...baseTransition,
delay: i * values.delayGap,
},
},
},
});
});
},
});How it works
Animation model
Revyme uses Motion's whileInView under the hood. The plugin sets three properties on each child node:
- initial — The starting state before the animation plays (faded, offset, scaled down)
- viewport — Controls when the animation triggers (
once: truemeans it only plays the first time) - transition — Spring or tween config with the staggered delay
The stagger effect
The key to the stagger is the delay calculation:
delay: i * values.delayGapWith delayGap: 0.1, the first child has 0s delay, the second has 0.1s, the third 0.2s, etc. This creates the cascading entrance.
Spring vs tween
Spring transitions feel more natural and don't need a fixed duration — they're controlled by stiffness and damping. Higher stiffness = snappier, higher damping = less bounce.
Tween transitions use a fixed duration with standard easing curves (easeOut, easeInOut, linear).
Customizing
You can extend this plugin in several ways:
- Add an
offsetXcontrol for horizontal slide-in effects - Add a
rotationcontrol for rotational entrance - Add a
directionselect (top, bottom, left, right) to change the slide direction - Add
blurto the initial state for a fade-blur effect