Documentation menu
Start
Getting started
animaly animates the DOM with springs, tweens, keyframes, inertia and timelines. Every animated value lives in a typed array column, and each element gets one style write per frame.
Install
The DOM package is @animaly/dom. It pulls in @animaly/core, the engine, which has no DOM dependency.
pnpm add @animaly/domnpm install @animaly/domyarn add @animaly/dombun add @animaly/domWithout a bundler, import it from a CDN that serves npm packages as ES modules:
<div class="box"></div>
<script type="module">
import { animate } from "https://esm.sh/@animaly/dom";
animate(".box", { x: 200, rotate: 90 }, { type: "spring" });
</script>Your first animation
animate takes a target, the values to reach and options. The target can be a CSS selector, an element, an array of elements or a NodeList. Without options you get a 0.3 second tween with easeInOut.
import { animate } from "@animaly/dom";
animate(".box", { x: 200, rotate: 90, opacity: 0.6 });Pass type: "spring" for a physical spring. Springs are solved in closed form, so they know their position and velocity at any time.
import { animate } from "@animaly/dom";
animate(".box", { x: 200 }, { type: "spring", stiffness: 300, damping: 20 });Many elements in one call
Arrays are keyframes: [16, 0] starts at 16 and ends at 0. stagger gives each element its own delay.
import { animate, stagger } from "@animaly/dom";
animate(".item", { y: [16, 0], opacity: [0, 1] }, { duration: 0.4, delay: stagger(0.06) });Waiting for an animation
Every call returns controls for all the values it started. They are awaitable, so you can run code after an exit animation without a callback.
import { animate } from "@animaly/dom";
const panel = document.querySelector<HTMLElement>(".panel")!;
await animate(panel, { opacity: 0, scale: 0.96 }, { duration: 0.2 });
panel.hidden = true;Frameworks and reduced motion
animaly has no framework bindings yet; call it from an effect and stop the animation when the component unmounts. Pass a duration of 0 to users who prefer reduced motion.
import { animate } from "@animaly/dom";
import { useEffect, useRef } from "react";
export function Card() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!ref.current) return;
const controls = animate(ref.current, { y: [24, 0], opacity: [0, 1] }, { type: "spring" });
return () => controls.stop();
}, []);
return <div ref={ref} className="card">Hello</div>;
}import { animate } from "@animaly/dom";
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
animate(".toast", { y: [40, 0], opacity: [0, 1] }, reduce ? { duration: 0 } : { type: "spring", stiffness: 260, damping: 22 });How it works
- Each animated value is a row in a set of
Float64Arraycolumns. Drivers step every moving row in one loop. - Springs, tweens, keyframes and inertia are solved analytically, so a new animation takes over the current velocity.
- Transforms combine into one
transformstring, and each element gets one write per frame. - Everything runs on the JS path by default. With
createAnimator({ maxAccelerated }), calls that only touchx,y,rotate,scaleandopacitycan run as CSS animations instead.