animate
animate springs the transform, opacity and blur of one or more elements toward target values. A second call on the same element does not restart anything: it retargets, and each property keeps the position and velocity it had.
damped owns the inline transform of every element it animates and replaces any existing one, so put a static transform on a wrapper. opacity is written once opacity is animated, and filter once blur is animated.
Functions
Section titled “Functions”animate
Section titled “animate”Animates the properties in values on target with a spring, interrupting and continuing any animation already running there.
export function animate( target: Element | readonly Element[], values: AnimationTargets, options: AnimateOptions = {},): AnimationControls| Parameter | Type | Default | Description |
|---|---|---|---|
target |
Element | readonly Element[] |
none | One element or an array. A NodeList or HTMLCollection is not an array and would be treated as a single element; spread it first. |
values |
AnimationTargets |
none | The target value of each property to animate. |
options |
AnimateOptions |
{} |
Spring, scheduler, reduced motion, start values and driver. |
Returns AnimationControls: a finished promise and a stop() function.
What it writes
| Group | Style | Format |
|---|---|---|
| transform | transform |
translate3d(Xpx, Ypx, 0) rotate(Rdeg) scale(SX, SY), where SX is scale * scaleX and SY is scale * scaleY. At rest: translate3d(0px, 0px, 0) rotate(0deg) scale(1, 1). |
| opacity | opacity |
the number as a string |
| filter | filter |
blur(Npx) while blur > 0.01, otherwise an empty string |
A group is written whole: animating only x writes the complete transform. A group that is never animated is never written. Each group is written at most once per element per frame, in the write phase. Elements without a style are skipped silently.
Behavior
- Ownership. Each call gets a token, and the latest call that targets a property (through
valuesorfrom) owns it. Retargeting reuses the sameSpringValue, so position and velocity carry over. Different properties run side by side. - Timing. The first frame of an animation renders the start state (
t = 0). - Thresholds. Per-property rest thresholds go in first and your own
restDeltaandrestSpeedoverride them individually. A100px move with the default spring rests after about 0.94 s, wherecreateSpringwith the default thresholds needs 1.16 s. - Validation first. Spring options, unknown property names and non-finite values are all checked before any element is touched, so a bad call never leaves an element half-animated.
- Idle. Once everything settles, the scheduler stops requesting frames. There is no cleanup to do and nothing to dispose; the element keeps its last inline styles.
- Scheduler. The first call on an element fixes its scheduler for good. See the overview.
- Reduced motion. Spatial properties jump to their target and
opacityandblurstill animate. A jump interrupts a running animation and resolves it. SeeAnimateOptions. - Server rendering. Importing is safe.
animateneeds anElement, so call it from an effect or an event handler.
Edge cases
- An unknown property name throws
TypeError('animate: unknown animatable property "foo"'). This is checked before theundefinedskip, so{ foo: undefined }throws too. - A non-number or non-finite value throws
RangeError('animate: "x" must be a finite number, received NaN'). A known key with the valueundefinedis skipped. fromuses the same checks with the labelanimate from.- Invalid spring options throw a
RangeErrorfrom the options validation. - With nothing to animate,
finishedresolves immediately.
Example
import { animate } from "@damped/core";
const card = document.querySelector<HTMLElement>(".card");if (card !== null) { const controls = animate(card, { x: 200, opacity: 0.5 }, { duration: 0.5, bounce: 0.2, from: { x: -40 } });
// Later, while it moves: the new call starts from the current position and velocity. animate(card, { x: 0, opacity: 1 });
await controls.finished; // resolves once superseded properties are accounted for controls.stop(); // no-op: a newer call owns everything}See also: AnimateOptions, AnimationTargets, AnimationControls, compositor, useSpring, Interruption and reversal, Reduced motion
AnimatableProperty
Section titled “AnimatableProperty”The names of the properties animate can move.
export type AnimatableProperty = "x" | "y" | "scale" | "scaleX" | "scaleY" | "rotate" | "opacity" | "blur";| Property | Group | Unit | Identity | restDelta / restSpeed |
Reduced motion |
|---|---|---|---|---|---|
x, y |
transform | px | 0 |
0.01 / 0.1 |
jumps |
rotate |
transform | degrees | 0 |
0.01 / 0.1 |
jumps |
scale, scaleX, scaleY |
transform | ratio | 1 |
0.0005 / 0.005 |
jumps |
opacity |
opacity | 0 to 1 | 1 |
0.0005 / 0.005 |
animates |
blur |
filter | px | 0 |
0.01 / 0.1 |
animates |
Behavior
- The identity is the value a property has when it is not animated, and what
enteranimates to by default. - The first time a value is created for
opacity, it starts from the element’s computed opacity. It is1whengetComputedStyleis missing or the result is not finite. scalemultipliesscaleXandscaleY.
Example
import type { AnimatableProperty } from "@damped/core";
const spatial: readonly AnimatableProperty[] = ["x", "y", "rotate", "scale", "scaleX", "scaleY"];
/** The properties that jump, rather than animate, when the user prefers reduced motion. */export function jumpsUnderReducedMotion(property: AnimatableProperty): boolean { return spatial.includes(property);}
console.log(jumpsUnderReducedMotion("x"), jumpsUnderReducedMotion("opacity")); // true falseSee also: AnimationTargets, animate, Reduced motion
AnimationTargets
Section titled “AnimationTargets”A partial map from property name to target value: what you pass as values to animate, and as the start and end values of enter and exit.
export type AnimationTargets = Partial<Record<AnimatableProperty, number>>;| Key | Type | Default | Description |
|---|---|---|---|
any AnimatableProperty |
number |
omitted: the property is left alone | The value to animate that property to. |
Behavior
- Values must be finite numbers.
undefinedskips the key. - Properties that are not mentioned are never written.
Example
import { animate, type AnimationTargets } from "@damped/core";
const lifted: AnimationTargets = { y: -8, scale: 1.02, opacity: 1 };const resting: AnimationTargets = { y: 0, scale: 1, opacity: 0.9 };
const tile = document.querySelector<HTMLElement>(".tile");if (tile !== null) { tile.addEventListener("pointerenter", () => animate(tile, lifted)); tile.addEventListener("pointerleave", () => animate(tile, resting));}See also: AnimatableProperty, animate, AnimateOptions
AnimateOptions
Section titled “AnimateOptions”The options of animate: either form of SpringOptions plus a scheduler, a reduced motion mode, start values and a driver.
export type AnimateOptions = SpringOptions & { scheduler?: Scheduler; reducedMotion?: "user" | "always" | "never"; from?: AnimationTargets; driver?: "js" | Driver;};| Option | Type | Default | Description |
|---|---|---|---|
| spring options | SpringOptions |
duration: 0.5, bounce: 0.15 |
Validated before any element is touched. restDelta and restSpeed default per property. |
scheduler |
Scheduler |
the shared frame |
Fixed per element by the first call. |
reducedMotion |
"user" | "always" | "never" |
"user" |
"user" follows prefers-reduced-motion, evaluated at call time and only when there is something to animate. A missing matchMedia means no preference. "always" jumps spatial properties without consulting matchMedia; "never" animates everything. |
from |
AnimationTargets |
none | Jumped to immediately before animating, also under reduced motion. |
driver |
"js" or Driver |
"js" |
"js" writes styles from the frame loop. Pass compositor to hand the sampled spring to the browser. |
Behavior
fromis applied as a jump. With the JS driver the inline style is written in the next frame’swritephase, so a call with onlyfromleavesstyle.transformempty until the first frame. With the compositor driver it is committed inline at once.- A
driverother than"js"must havesupportsandplayfunctions, otherwiseanimatethrowsTypeError('animate: driver must be "js" or a driver such as the exported compositor'). - Any
reducedMotionstring other than"user"and"always"behaves as"never". The types forbid it. layout,morph,enterandexitaccept these options withoutdriver.layout,morphandenteralso dropfrom;exitkeeps it.
Example
import { animate, type AnimateOptions } from "@damped/core";
const options: AnimateOptions = { duration: 0.4, bounce: 0.1, reducedMotion: "user", from: { opacity: 0, y: 12 },};
const toast = document.querySelector<HTMLElement>(".toast");if (toast !== null) { void animate(toast, { opacity: 1, y: 0 }, options);}See also: animate, SpringOptions, LayoutOptions, compositor, Reduced motion
AnimationControls
Section titled “AnimationControls”What animate and layout return: a promise for the end and a way to freeze the motion.
export interface AnimationControls { readonly finished: Promise<void>; stop(): void;}| Member | Type | Description |
|---|---|---|
finished |
Promise<void> |
Resolves when every property that this call animates has settled or been superseded. It never rejects. |
stop() |
() => void |
Freezes the properties this call still owns, with zero velocity. |
Behavior
finishedresolves, it does not reject, when the properties are superseded. It does not tell you which happened; usemorphorexitwhen you need that.- Properties that jumped under reduced motion create no wait.
stop()leaves a property alone when a newer call has taken it over: that property keeps animating.finishedstill resolves.stop()after everything settled changes nothing. It freezes every element in the call.- The element keeps its last inline styles after
stop().
Example
import { animate, type AnimationControls } from "@damped/core";
const panel = document.querySelector<HTMLElement>(".panel");if (panel !== null) { const controls: AnimationControls = animate(panel, { x: 320 }, { duration: 0.8 });
document.addEventListener("keydown", (event) => { if (event.key === "Escape") controls.stop(); // freeze where it is });
await controls.finished; console.log("done or interrupted");}See also: animate, LayoutSnapshot, MorphControls