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
pnpm add @animaly/motion// 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.
import { animate } from "@animaly/motion";
// x springs and opacity tweens, as in motion, without any options.
animate(".card", { x: 120, opacity: 1 });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.
import { animate } from "@animaly/motion";
animate(".card", { y: [40, 0], opacity: 1 }, { type: "spring", bounce: 0.35, visualDuration: 0.4 });import { animate } from "@animaly/motion";
animate(".card", { x: 160 }, { type: "spring", stiffness: 260, damping: 18, mass: 1.2 });Repeats, instant changes and end values
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 });import { animate } from "@animaly/motion";
animate(".card", { x: 80, opacity: 1 }, { type: false });import { animate } from "@animaly/motion";
animate(".card", { opacity: 0, transitionEnd: { display: "none" } }, { duration: 0.3 });Stagger and sequences
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.
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" }],
]);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.
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 startValues and helpers
animate also takes numbers, strings, objects and motion values, with onUpdate for each. Motion values have motion's set and jump.
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) });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 velocityimport { 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.5Bezier 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.
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.
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
minormaxbounces 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: atvelocity: 1200, max: 150motion 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, butget()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.