animaly
Documentation menu

Packages

Switching from motion

@animaly/motion is motion's animate, motionValue and stagger running on animaly, with motion's defaults, option names and controls. Change the import and keep your code.

Install

Install
pnpm add @animaly/motion
Switch an existing import
// Before
// import { animate, stagger } from "motion";

// After
import { animate, stagger } from "@animaly/motion";

animate(".card", { x: 120, opacity: 1 });

Use @animaly/motion to move existing motion code over without rewriting it. For new code, @animaly/dom is the smaller API that the rest of these docs describe. Both run on the same engine.

Transitions

Without options, values get motion's default transitions: transforms spring, opacity and colors tween. Per-value transitions take motion's keys, with default for the rest.

motion's default transitions
import { animate } from "@animaly/motion";

// x springs and opacity tweens, as in motion, without any options.
animate(".card", { x: 120, opacity: 1 });
A transition per value
import { animate } from "@animaly/motion";

animate(".card", { x: 200, opacity: 1, rotate: 8 }, {
  default: { type: "spring", bounce: 0.3 },
  opacity: { duration: 0.2, ease: "linear" },
  rotate: { delay: 0.1 },
});

Springs

Springs take bounce and visualDuration, or stiffness, damping and mass, and use motion's solver, so they settle where and when motion's do.

Springs by bounce and visualDuration
import { animate } from "@animaly/motion";

animate(".card", { y: [40, 0], opacity: 1 }, { type: "spring", bounce: 0.35, visualDuration: 0.4 });
Springs by physics
import { animate } from "@animaly/motion";

animate(".card", { x: 160 }, { type: "spring", stiffness: 260, damping: 18, mass: 1.2 });

Repeats, instant changes and end values

Repeat, reverse and mirror
import { animate } from "@animaly/motion";

animate(".card", { opacity: 1 }, { duration: 0.2 });
animate(".card", { x: [0, 100] }, { duration: 0.8, repeat: 3, repeatType: "mirror", repeatDelay: 0.2 });
Jump without animating
import { animate } from "@animaly/motion";

animate(".card", { x: 80, opacity: 1 }, { type: false });
Set values when the animation ends
import { animate } from "@animaly/motion";

animate(".card", { opacity: 0, transitionEnd: { display: "none" } }, { duration: 0.3 });

Stagger and sequences

Stagger from the center
import { animate, stagger } from "@animaly/motion";

animate(".dot", { scale: [1, 1.4, 1] }, { duration: 0.6, delay: stagger(0.05, { from: "center" }) });

Sequences take at, labels, motion values, callback segments and spring segments.

Sequences with at and labels
import { animate } from "@animaly/motion";

animate([
  [".title", { y: [20, 0], opacity: [0, 1] }],
  [".body", { opacity: [0, 1] }, { at: "-0.1" }],
  "buttons",
  [".cta", { scale: [0.9, 1], opacity: [0, 1] }, { at: "buttons" }],
]);
Motion values and callbacks in a sequence
import { animate, motionValue } from "@animaly/motion";

const progress = motionValue(0);
progress.on("change", (latest) => console.log(latest));

animate([
  [progress, 100, { duration: 0.5 }],
  [(latest: number) => console.log("callback", latest), { duration: 0.3 }],
  [".title", { opacity: [0, 1] }],
]);

Controls

Controls have motion's time, speed, duration, iterationDuration and state, and play again from the start when you call play() after they finish.

Controls with motion's fields
import { animate } from "@animaly/motion";

const controls = animate(".card", { x: 200, opacity: 1 }, { duration: 1, repeat: 1 });

console.log(controls.state); // "running"
console.log(controls.duration, controls.iterationDuration);

controls.pause();
controls.time = 0.5;
controls.speed = 2;
controls.play();

await controls;
console.log(controls.state); // "finished"
controls.play(); // replays from the start

Values and helpers

animate also takes numbers, strings, objects and motion values, with onUpdate for each. Motion values have motion's set and jump.

Numbers, strings and objects
import { animate } from "@animaly/motion";

animate(0, 100, { duration: 0.5, onUpdate: (latest) => console.log(latest) });
animate("#7c3aed", "#db2777", { onUpdate: (color) => console.log(color) });

const camera = { x: 0, zoom: 1 };
animate(camera, { x: 400, zoom: 2 }, { onUpdate: () => console.log(camera.x, camera.zoom) });
Motion values with set and jump
import { animate, motionValue } from "@animaly/motion";

const x = motionValue(0);
x.on("change", (latest) => console.log(latest));

animate(x, 100, { type: "spring" });
x.set(50); // a running animation writes over this on its next frame
x.jump(0); // stops the animation and resets velocity
mix, interpolate and transform
import { interpolate, mix, transform } from "@animaly/motion";

console.log(mix(0, 100, 0.25)); // 25
console.log(mix("#000000", "#ffffff")(0.5)); // rgba(180, 180, 180, 1)

const toOpacity = interpolate([0, 100, 200], [0, 1, 0]);
console.log(toOpacity(50)); // 0.5

console.log(transform(150, [100, 200], [0, 1])); // 0.5

Bezier eases

Cubic bezier eases use animaly's solver by default. createMotion({ bezier: "motion" }) returns an animate that uses motion's own solver, for output that matches motion exactly.

Exact parity for cubic bezier eases
import { createMotion } from "@animaly/motion";

// Uses motion's own bezier solver instead of animaly's.
const { animate } = createMotion({ bezier: "motion" });

animate(".card", { x: 200, opacity: 1 }, { duration: 0.6, ease: [0.22, 1, 0.36, 1] });

Gestures and scroll

hover, press, inView, resize, scroll and scrollInfo are exported too; they are the ones from gestures and scroll.

Gestures and scroll from the same package
import { animate, hover, scroll } from "@animaly/motion";

hover(".card", (element) => {
  animate(element, { scale: 1.05 });
  return () => animate(element, { scale: 1 });
});

scroll(animate(".card", { opacity: [0.4, 1] }, { ease: "linear" }));

Compatibility

The package is checked against motion v13.4.6: 116 of motion's own tests (animate, sequences, motion values, elements and stagger) run against it, and 30 transitions are compared with motion's animate frame by frame, equal within 1e-6.

Known differences

  • Inertia with min or max bounces from the exact moment it crosses the bound. motion finds the crossing while stepping the animation every 50 ms, so its spring starts later and overshoots further: at velocity: 1200, max: 150 motion peaks at 285.9 and animaly at 231.9.
  • A value changed with set() while it animates is overwritten on the next frame, as in motion, but get() in between returns the animated value.

Not done yet

Repeating springs, %, vw and em on transforms, and attachTimeline. Each throws an error that names it.

Parts of motion are ported under its MIT license; the package's NOTICE file lists them. The API can still change before 1.0.