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

  1. User selects a frame that contains child elements (cards, text blocks, images, etc.)
  2. The plugin reads all children and sorts them by render order
  3. Each child gets a whileInView animation with an incrementing delay
  4. 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: true means 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.delayGap

With 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 offsetX control for horizontal slide-in effects
  • Add a rotation control for rotational entrance
  • Add a direction select (top, bottom, left, right) to change the slide direction
  • Add blur to the initial state for a fade-blur effect