Skip to content

Examples

Each example is a complete script and its rendered result. The code shown is the exact script that produced the video. Open any of them in the Studio to preview and edit them live.

A title that rises in

ts
composition({ width: 1280, height: 720, fps: 30, duration: "2.5s", background: "#0E1116" }, (comp) => {
  const [cx, cy] = comp.center;
  comp.add(text("Hello, Wiggle", { size: 84, weight: 700, color: "#FFFFFF" }))
    .x(cx).y(cy)
    .enter.rise({ by: 24, ease: { damping: 200 } });
});

A shape with a gradient

ts
composition({ width: 1280, height: 720, fps: 30, duration: "2.5s", background: "#0E1116" }, (comp) => {
  const [cx, cy] = comp.center;
  comp.add(rect({ size: [360, 220], radius: 18, fill: gradient("#3B82F6", "#9333EA") }))
    .x(cx).y(cy)
    .enter.pop({ from: 0.6, ease: { damping: 12 } });
});

A staggered word reveal

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3s", background: "#F2F2F3" }, (comp) => {
  const [cx, cy] = comp.center;
  comp.add(text("Compose once. Switch in a click.", {
      size: 64, weight: 800, color: "#1B1535", tracking: -2, maxWidth: 900,
    }))
    .x(cx).y(cy)
    .words().enter.rise({ by: 22, ease: { damping: 200 } }).stagger("0.06s");
});

Scale and rotate at once

Animations on different properties run together.

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3s", background: "#0E1116" }, (comp) => {
  const [cx, cy] = comp.center;
  // Scale and rotate at once, then nothing else.
  comp.add(rect({ size: 180, radius: 16, fill: "#22D3EE" }))
    .x(cx).y(cy)
    .scaleTo(1.6, { at: "0.4s", duration: "1.2s" })
    .rotateTo(180, { at: "0.4s", duration: "1.2s" });
});

Scale, then rotate

Animations on different properties are sequenced by their start times. The scale runs first and holds while the rotation waits its turn.

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3s", background: "#0E1116" }, (comp) => {
  const [cx, cy] = comp.center;
  // Different properties, sequenced by their start times: scale up first,
  // then rotate. The scale holds while the rotation waits its turn.
  comp.add(rect({ size: 180, radius: 16, fill: "#22D3EE" }))
    .x(cx).y(cy)
    .scaleTo(1.6, { at: "0.4s", duration: "0.7s" })
    .rotateTo(90, { at: "1.4s", duration: "0.7s" });
});

Easing: a curve or a spring

Every place that animates takes one ease parameter. It is either a named curve ("linear" | "in" | "out" | "inOut") or a spring ({ stiffness?, damping?, mass? }). A curve eases over its duration; a spring overshoots and settles, settling naturally when no duration is given. The same ease applies to keyframes, moveTo / scaleTo / rotateTo, interpolate, and the entrance verbs.

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3s", background: "#0E1116" }, (comp) => {
  const [cx, cy] = comp.center;
  // One `ease` parameter, two kinds. A named curve eases over its duration; a
  // spring overshoots and settles (with no duration it settles naturally). The
  // same `ease` works on keyframes, the tween verbs, interpolate, and the
  // entrance verbs.
  comp.add(text("curve", { size: 36, weight: 700, color: "#94A3B8" })).x(cx - 300).y(cy - 150);
  comp.add(rect({ size: 150, radius: 16, fill: "#22D3EE" }))
    .x(cx - 300).y(cy)
    .rotateTo(360, { at: "0.4s", duration: "1.6s", ease: "inOut" });

  comp.add(text("spring", { size: 36, weight: 700, color: "#94A3B8" })).x(cx + 300).y(cy - 150);
  comp.add(rect({ size: 150, radius: 16, fill: "#E0604F" }))
    .x(cx + 300).y(cy)
    .rotateTo(360, { at: "0.4s", ease: { stiffness: 120, damping: 8 } });
});

Moves in sequence

Several tweens on the same property chain, each continuing from where the last ended.

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3.5s", background: "#0E1116" }, (comp) => {
  const cy = comp.height / 2;
  // Each move continues from where the last ended.
  comp.add(ellipse({ size: 90, fill: "#E0604F" }))
    .x(240).y(cy)
    .moveTo(1040, cy, { at: "0.6s", duration: "0.9s" })
    .moveTo(1040, cy - 200, { at: "1.8s", duration: "0.9s" });
});

Rotating around a corner

anchor(x, y) sets the pivot for rotation and scale, as a fraction of the layer's bounds (the default is the center, 0.5, 0.5). With the pivot at the bottom-center, the bar swings from its base like a metronome rather than spinning around its middle. In the studio the pivot is drawn as a crosshair on the selected layer and can be dragged.

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3s", background: "#0E1116" }, (comp) => {
  const [cx, cy] = comp.center;
  // anchor() sets the pivot for rotation and scale, as a fraction of the
  // layer's bounds. Here the pivot is the bottom-center, so the bar swings from
  // its base like a metronome instead of spinning around its middle.
  comp.add(rect({ size: [44, 240], radius: 10, fill: "#E0604F" }))
    .anchor(0.5, 1)
    .x(cx).y(cy + 120)
    .rotateTo(38, { at: "0.2s", duration: "0.8s" })
    .rotateTo(-38, { at: "1.1s", duration: "0.9s" })
    .rotateTo(0, { at: "2.1s", duration: "0.7s" });
});

A group and its children, animated separately

A group has the same properties and tweens as a node, and its transform composes onto its children. Here the group turns one way while each child turns the other, so they orbit the center while staying upright. A child's world rotation is its own plus the parent's.

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3s", background: "#0E1116" }, (comp) => {
  const [cx, cy] = comp.center;
  // The group turns one way; each child turns the other. A child's world
  // rotation is its own plus the parent's, so the squares orbit the center
  // while staying upright.
  const ring = comp.group({ x: cx, y: cy });
  ring.add(rect({ size: 120, radius: 12, fill: "#E0604F" })).x(-200).y(0).rotateTo(360, { duration: "3s" });
  ring.add(rect({ size: 120, radius: 12, fill: "#22D3EE" })).x(200).y(0).rotateTo(360, { duration: "3s" });
  ring.rotateTo(-360, { duration: "3s" });
});

A window tilted in perspective

A drawn layer tilts in space with rotationX or rotationY and a perspective distance. Here the tilt eases from steep to shallow while the layer slides in.

ts
composition({ width: 1280, height: 720, fps: 30, duration: "3s", background: "#0B1020" }, (comp) => {
  const cy = comp.height / 2;

  comp.add(text("Show your app", { size: 72, weight: 800, color: "#FFFFFF", align: "left", tracking: -2, maxWidth: 480 }))
    .x(96).y(cy - 70)
    .enter.rise({ by: 26, ease: { damping: 200 } });
  comp.add(text("at any angle.", { size: 72, weight: 800, color: "#38BDF8", align: "left", tracking: -2 }))
    .x(96).y(cy + 10)
    .at("0.12s").enter.rise({ by: 26, ease: { damping: 200 } });
  comp.add(text("A perspective tilt, swinging as it plays.", { size: 26, weight: 500, color: "#94A3B8", align: "left", maxWidth: 460 }))
    .x(96).y(cy + 110)
    .at("0.3s").enter.fade();

  // The panel slides in, then swings through perspective. The wide swing and the
  // close camera make the tilt obvious.
  comp.add(rect({ size: [640, 430], radius: 22, fill: gradient("#1D4ED8", "#0EA5E9") }))
    .x(comp.width + 320).y(cy)
    .perspective(820)
    .rotationY.keys([
      { at: "0s", to: 52 },
      { at: "1.5s", to: -22, ease: "inOut" },
      { at: "3s", to: 52, ease: "inOut" },
    ])
    .moveTo(comp.width - 300, cy, { at: "0s", duration: "1s" });
});