Documentation menu
Guides
Stagger and timelines
stagger spreads one animation across many elements with growing delays. A sequence is a list of animations placed on one timeline, controlled as a single animation.
Stagger
stagger(each) returns a function that delay accepts. Each element waits each seconds longer than the one before it.
import { animate, stagger } from "@animaly/dom";
animate(".item", { y: [12, 0], opacity: [0, 1] }, { duration: 0.35, delay: stagger(0.06) });Where the stagger starts
from picks the element with no delay: "first" (the default), "last", "center" or an index. Delays grow with the distance from that element.
import { animate, stagger } from "@animaly/dom";
animate(".dot", { scale: [1, 1.6, 1] }, { duration: 0.5, delay: stagger(0.05, { from: "center" }) });import { animate, stagger } from "@animaly/dom";
animate(".item", { x: [-24, 0], opacity: [0, 1] }, { duration: 0.3, delay: stagger(0.05, { from: "last" }) });import { animate, stagger } from "@animaly/dom";
const clicked = 2;
animate(".dot", { y: [0, -16, 0] }, { duration: 0.4, delay: stagger(0.04, { from: clicked }) });A delay before the first element
import { animate, stagger } from "@animaly/dom";
animate(".item", { opacity: [0, 1] }, { duration: 0.3, delay: stagger(0.08, { startDelay: 0.4 }) });Eased delays
ease distributes the delays along a curve instead of evenly. It accepts the same values as a tween's ease.
import { animate, stagger } from "@animaly/dom";
// delays follow an easeOut curve across the row, so later elements bunch up
animate(".dot", { y: [20, 0], opacity: [0, 1] }, { duration: 0.4, delay: stagger(0.1, { ease: "easeOut" }) });Staggered springs
import { animate, stagger } from "@animaly/dom";
animate(".item", { x: [40, 0], opacity: [0, 1] }, { type: "spring", stiffness: 320, damping: 24, delay: stagger(0.05) });Your own delay function
delay also accepts any function of (index, total) that returns seconds. This one measures the distance from the middle of a 5 by 5 grid, so the ripple is round instead of running along the list order.
import { animate } from "@animaly/dom";
const columns = 5;
const center = { x: 2, y: 2 };
const fromCenter = (index: number): number => {
const x = index % columns;
const y = Math.floor(index / columns);
return Math.hypot(x - center.x, y - center.y) * 0.06;
};
animate(".cell", { scale: [0.6, 1], opacity: [0.2, 1] }, { duration: 0.45, delay: fromCenter });Staggered exit
import { animate, stagger } from "@animaly/dom";
const items = Array.from(document.querySelectorAll<HTMLElement>(".item"));
await animate(items, { x: 24, opacity: 0 }, { duration: 0.2, delay: stagger(0.04, { from: "last" }) });
items.forEach((item) => item.remove());Sequences
Pass an array of segments to animate. Each segment is [target, values] or [target, values, options]. By default a segment starts when the previous one ends.
import { animate } from "@animaly/dom";
animate([
[".a", { x: 120 }, { duration: 0.4 }],
[".b", { x: 120 }, { duration: 0.4 }],
[".c", { x: 120 }, { duration: 0.4 }],
]);Placing segments with at
at moves a segment on the timeline. A number is an absolute time in seconds. "+0.2" starts 0.2 seconds after the previous segment ends, "-0.15" overlaps it by 0.15 seconds, and "<" starts together with the previous segment.
import { animate } from "@animaly/dom";
animate([
[".a", { x: 120 }, { duration: 1 }],
[".b", { opacity: 0.3 }, { duration: 0.3, at: 0.5 }],
]);import { animate } from "@animaly/dom";
animate([
[".a", { y: -20 }, { duration: 0.3 }],
[".b", { y: -20 }, { duration: 0.3, at: "+0.2" }],
[".c", { y: -20 }, { duration: 0.3, at: "-0.15" }],
]);import { animate } from "@animaly/dom";
animate([
[".a", { x: 100 }, { duration: 0.5 }],
[".b", { rotate: 90 }, { duration: 0.5, at: "<" }],
]);Labels
A string in the list marks the current end of the timeline with a name. { name, at } places a label at a time of your choice. Segments refer to a label through at.
import { animate } from "@animaly/dom";
animate([
[".a", { x: 100 }, { duration: 0.4 }],
"reveal",
[".b", { opacity: [0, 1] }, { duration: 0.3, at: "reveal" }],
[".c", { opacity: [0, 1] }, { duration: 0.3, at: "reveal" }],
{ name: "settle", at: 1.2 },
[".a", { x: 0 }, { type: "spring", at: "settle" }],
]);Options per property
Inside a segment, a key named after a property overrides the options for that property only. Here x springs while opacity tweens.
import { animate } from "@animaly/dom";
animate([
[".a", { x: 160, opacity: 0.4 }, { duration: 0.6, x: { type: "spring", stiffness: 220, damping: 14 } }],
]);Repeat and delay
import { animate } from "@animaly/dom";
animate(
[
[".a", { scale: [1, 1.2, 1] }, { duration: 0.4 }],
[".b", { scale: [1, 1.2, 1] }, { duration: 0.4 }],
[".c", { scale: [1, 1.2, 1] }, { duration: 0.4 }],
],
{ repeat: 2, delay: 0.5 },
);Default options for every segment
defaultTransition applies to segments that don't set their own options.
import { animate } from "@animaly/dom";
animate(
[
[".a", { y: -24 }],
[".b", { y: -24 }],
[".c", { y: -24 }, { duration: 0.8 }],
],
{ defaultTransition: { duration: 0.25, ease: "easeOut" } },
);Staggered segments
A segment that targets several elements can carry a stagger in its own delay.
import { animate, stagger } from "@animaly/dom";
animate([
[".list", { opacity: [0, 1] }, { duration: 0.2 }],
[".item", { x: [-16, 0], opacity: [0, 1] }, { duration: 0.3, delay: stagger(0.05) }],
]);Staggered delays inside a sequence work for tweens, keyframes and springs. Sequences can't contain inertia.
A hero entrance
import { animate, stagger } from "@animaly/dom";
animate([
[".title", { y: [24, 0], opacity: [0, 1] }, { duration: 0.5, ease: "easeOut" }],
[".lede", { y: [16, 0], opacity: [0, 1] }, { duration: 0.4, at: "-0.3" }],
"cards",
[".card", { y: [32, 0], opacity: [0, 1] }, { duration: 0.45, ease: "easeOut", at: "cards", delay: stagger(0.08) }],
[".cta", { scale: [0.9, 1], opacity: [0, 1] }, { duration: 0.3, at: "+0.1" }],
]);After a sequence
A sequence returns one set of controls for all its segments. Await it, or pass onComplete.
import { animate } from "@animaly/dom";
const next = document.querySelector<HTMLButtonElement>(".next")!;
await animate([
[".a", { opacity: [0, 1] }, { duration: 0.3 }],
[".b", { opacity: [0, 1] }, { duration: 0.3 }],
[".c", { opacity: [0, 1] }, { duration: 0.3 }],
]);
next.disabled = false;import { animate } from "@animaly/dom";
const status = document.querySelector<HTMLElement>(".status")!;
animate(
[
[".a", { x: 80 }, { duration: 0.3 }],
[".b", { x: 80 }, { duration: 0.3 }],
],
{ onComplete: () => (status.textContent = "Done") },
);duration covers the whole timeline, including repeats and the sequence delay.
import { animate } from "@animaly/dom";
const controls = animate(
[
[".a", { x: 100 }, { duration: 0.5 }],
[".b", { x: 100 }, { duration: 0.5 }],
],
{ repeat: 1, delay: 0.2 },
);
console.log(controls.duration); // 2.2: two 0.5 s segments, played twice, after a 0.2 s delayOptions
| Option | Type | Default | What it does |
|---|---|---|---|
| each | number | 0.1 | Seconds between neighbouring elements. The first argument of stagger. |
| from | "first" | "last" | "center" | number | "first" | The element that starts with no delay. |
| startDelay | number | 0 | Seconds added to every delay. |
| ease | Ease | none | Spreads the delays along an easing curve instead of evenly. |
| Option | Type | Default | What it does |
|---|---|---|---|
| at | number | "+n" | "-n" | "<" | label | none | Where the segment starts. Defaults to the end of the previous segment. |
| type, duration, ease, ... | AnimateOptions | none | Any tween, keyframes or spring option for the whole segment. |
| [property] | AnimateOptions | none | Options for one property, for example { x: { type: "spring" } }. |
| Option | Type | Default | What it does |
|---|---|---|---|
| repeat | number | 0 | How many extra times the whole timeline plays. |
| delay | number | 0 | Seconds before the timeline starts. |
| defaultTransition | AnimateOptions | none | Options for segments that set none. |
| onComplete | () => void | none | Called when the timeline finishes. |