animaly
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
pnpm add @animaly/dom
npm
npm install @animaly/dom
yarn
yarn add @animaly/dom
bun
bun add @animaly/dom

Without a bundler, import it from a CDN that serves npm packages as ES modules:

index.html, no build step
<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.

main.ts
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.

A spring instead of a tween
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.

Many elements, one call
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.

Wait for it to finish
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.

Inside a React component
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>;
}
Respect reduced motion
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 Float64Array columns. 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 transform string, and each element gets one write per frame.
  • Everything runs on the JS path by default. With createAnimator({ maxAccelerated }), calls that only touch x, y, rotate, scale and opacity can run as CSS animations instead.

Next steps