animaly
Documentation menu

Guides

SVG

SVG elements take the same animate call as HTML elements. Attributes animate by name, viewBox animates as four numbers, and three extra values draw a path's stroke.

Attributes

Pass an SVG attribute by its camelCase name: cx, cy, r, rx, x1, width, fill, stroke, strokeWidth, fillOpacity and so on. animaly writes them as attributes, so they work on every SVG element that has them.

Move a circle with cx and cy
import { animate } from "@animaly/dom";

animate(".dot", { cx: 160, cy: 60 }, { type: "spring" });
Grow a circle's radius
import { animate } from "@animaly/dom";

animate(".dot", { r: [16, 40, 24] }, { duration: 0.8 });
Resize a rectangle
import { animate } from "@animaly/dom";

animate(".bar", { width: 180, rx: 10 }, { duration: 0.6, ease: "easeOut" });

Fill and stroke

fill and stroke take hex, rgb() or rgba() colors, like every other color in animaly.

Change fill and stroke colors
import { animate } from "@animaly/dom";

animate(".dot", { fill: "#ec4899", stroke: "#7c3aed" }, { duration: 0.4 });
Thicken a stroke on hover
import { animate } from "@animaly/dom";

const dot = document.querySelector<SVGCircleElement>(".dot")!;

dot.addEventListener("pointerenter", () => animate(dot, { strokeWidth: 6 }, { duration: 0.15 }));
dot.addEventListener("pointerleave", () => animate(dot, { strokeWidth: 2 }, { duration: 0.15 }));

Transforms and opacity

x, y, rotate, scale and opacity work on SVG elements as on HTML. SVG elements rotate around the origin of the drawing by default; set transform-box: fill-box and transform-origin: center in CSS to rotate around the element itself.

Rotate and scale an SVG element
import { animate } from "@animaly/dom";

// transform-box: fill-box in CSS makes the element rotate around its own center.
animate(".square", { rotate: 90, scale: 1.3 }, { type: "spring", stiffness: 200, damping: 12 });
Fade a whole group
import { animate } from "@animaly/dom";

animate(".icons", { opacity: [0, 1] }, { duration: 0.4 });

viewBox

viewBox animates its four numbers, which pans and zooms the drawing without touching its content.

Zoom in by animating viewBox
import { animate } from "@animaly/dom";

animate(".map", { viewBox: "250 30 100 100" }, { duration: 0.8, ease: "easeInOut" });
Pan across a wide drawing
import { animate } from "@animaly/dom";

animate(".strip", { viewBox: ["0 0 200 100", "600 0 200 100"] }, { duration: 2 });

Drawing paths

Three values control which part of a stroke is visible. They work on any element with a stroke: paths, lines, circles, rectangles and polylines.

  • pathLength is the visible part of the stroke, from 0 (nothing) to 1 (all of it).
  • pathSpacing is the gap after each visible part, also as a fraction of the path. It repeats the dash along the path.
  • pathOffset moves where the visible part starts.
Draw a path
import { animate } from "@animaly/dom";

// pathLength 0 hides the stroke, 1 shows all of it.
animate(".line", { pathLength: [0, 1] }, { duration: 1.2, ease: "easeInOut" });
Erase a path
import { animate } from "@animaly/dom";

animate(".line", { pathLength: [1, 0] }, { duration: 0.6, ease: "easeIn" });
Draw a dashed line with pathSpacing
import { animate } from "@animaly/dom";

// Each dash is 0.1 of the path long, followed by a 0.1 gap.
animate(".line", { pathLength: 0.1, pathSpacing: 0.1 });
Move a dash along a path with pathOffset
import { animate } from "@animaly/dom";

animate(".line", { pathLength: 0.2, pathOffset: [0, 0.8] }, {
  duration: 1.5,
  repeat: Infinity,
  repeatType: "reverse",
});

animaly sets the pathLength attribute to 1 and writes stroke-dasharray and stroke-dashoffset for you, so the values are fractions whatever the real length of the path.

Common patterns

A checkmark

Await the ring, then draw the tick.

Draw a checkmark after saving
import { animate } from "@animaly/dom";

await animate(".ring", { pathLength: [0, 1] }, { duration: 0.5, ease: "easeOut" });
animate(".tick", { pathLength: [0, 1] }, { duration: 0.3, ease: "easeOut" });

A progress ring

Animate pathLength on a circle to the progress value. A spring makes each update glide from wherever the ring is.

Fill a progress ring
import { animate } from "@animaly/dom";

const setProgress = (value: number) =>
  animate(".meter", { pathLength: value }, { type: "spring", stiffness: 120, damping: 20 });

setProgress(0.65);
Count up next to the ring
import { animate } from "@animaly/dom";

const percent = document.querySelector<HTMLOutputElement>(".percent")!;

animate(".meter", { pathLength: [0, 0.8] }, {
  duration: 1.2,
  ease: "easeOut",
  onUpdate: (latest, name) => {
    if (name !== "pathLength") return;
    percent.textContent = `${Math.round(Number(latest) * 100)}%`;
  },
});

Morphing between numeric attributes

Any numeric attribute can move, so lines, ellipses and rectangles can change into each other's positions and proportions.

Morph a line between two positions
import { animate } from "@animaly/dom";

animate(".beam", { x1: 100, y1: 10, x2: 100, y2: 90 }, { type: "spring", stiffness: 180, damping: 14 });
Turn a circle into an ellipse
import { animate } from "@animaly/dom";

animate(".blob", { rx: [40, 80, 40], ry: [40, 24, 40] }, { duration: 0.9, repeat: 2 });

Several paths in turn

Draw several paths one after another
import { animate, stagger } from "@animaly/dom";

animate(".stroke", { pathLength: [0, 1] }, { duration: 0.4, delay: stagger(0.12) });
Bounce a row of dots from the center
import { animate, stagger } from "@animaly/dom";

animate(".bead", { cy: [30, 12, 30] }, { duration: 0.6, delay: stagger(0.08, { from: "center" }), repeat: Infinity });
Draw a logo, then fill it in
import { animate } from "@animaly/dom";

await animate(".mark", { pathLength: [0, 1] }, { duration: 1, ease: "easeInOut" });
animate(".mark", { fillOpacity: 1 }, { duration: 0.4 });