Appearance
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" });
});