Skip to content

Start without GSAP

@motly/core is the engine: bursts, shapes and swirls you describe as JSON, which motly generates, samples and paints. It has no runtime dependencies and never touches the DOM itself; Renderers, in their own entries, do that.

Using GSAP? Start with GSAP instead: @motly/gsap puts these bursts in GSAP’s timelines, scrubbing and cleanup.

Terminal window
npm install @motly/core
import { Burst, rand } from '@motly/core';
import { SVGRenderer } from '@motly/core/svg';
const renderer = new SVGRenderer(document.querySelector('#stage'));
const burst = new Burst(
{
kind: 'burst',
count: 12,
radius: [0, 100],
restAt: 0.4,
children: {
kind: 'circle',
radius: [rand(4, 9), 0],
fill: ['deeppink', 'gold'],
duration: 0.8,
},
},
{ renderer, origin: { x: 150, y: 150 } },
);
burst.play();

The first argument is the Spec: plain JSON. An array is Keyframes, so radius: [0, 100] moves from 0 to 100 over the burst, and fill: ['deeppink', 'gold'] fades from one color to the other. A burst lasts as long as its longest-running Child, so changing a Child’s duration changes the burst’s.

rand(4, 9) is a Descriptor, not a number: it survives JSON.stringify and resolves from the Instance’s Seed, so passing seed: 7 beside renderer draws the same burst every time. each([...]), also from @motly/core, hands values out to the Children in turn.

The second argument binds the Spec to a Renderer and an Origin, in the Renderer’s CSS pixels. play() returns a promise that settles when the burst reaches its end, and leaves the last frame drawn. Call burst.destroy() when you are done with it, to remove what it drew.

Each Renderer paints into an element you own, and one Renderer can host any number of Instances.

  • SVGRenderer, from @motly/core/svg, draws into an <svg>. Its output can be inspected, and it handles up to a few hundred Elements.
  • CanvasRenderer, from @motly/core/canvas, paints onto a <canvas>, for thousands.
  • AutoRenderer, from @motly/core/auto, layers a container element you give it, and picks one of the two for each Instance by its Element count.

A burst of bursts, 288 Elements, on a canvas:

import { Burst, each, rand } from '@motly/core';
import { CanvasRenderer } from '@motly/core/canvas';
const renderer = new CanvasRenderer(document.querySelector('#stage'));
// 24 bursts of 12: 288 Elements, which a canvas paints faster than SVG would.
const burst = new Burst(
{
kind: 'burst',
count: 24,
radius: [0, 90],
children: {
kind: 'burst',
count: 12,
radius: [0, rand(20, 45)],
delay: rand(0, 0.3),
children: {
kind: 'circle',
radius: [rand(2, 4), 0],
fill: each(['#ffd166', '#ef476f', '#06d6a0', '#118ab2']),
duration: rand(0.6, 1),
},
},
},
{ renderer, origin: { x: 180, y: 150 } },
);
burst.play();

A viewer who prefers reduced motion sees the Spec’s still Resting frame instead of motion: the frame at restAt, a progress from 0 to 1, or the last frame when it is left out. A burst’s last frame is usually empty, so set restAt where it still shows something.

reducedMotion: 'always' or 'never' in the second argument forces either, for a demo or a test:

import { Burst, rand } from '@motly/core';
import { SVGRenderer } from '@motly/core/svg';
const spec = {
kind: 'burst',
count: 12,
radius: [0, 80],
// Where the Resting frame is, as a progress from 0 to 1. Its last frame would be empty.
restAt: 0.4,
children: { kind: 'circle', radius: [rand(4, 9), 0], fill: 'gold', duration: 1.2 },
};
for (const [id, reducedMotion] of [
['#moving', 'never'],
['#resting', 'always'],
]) {
const renderer = new SVGRenderer(document.querySelector(id));
const burst = new Burst(spec, { renderer, origin: { x: 110, y: 110 }, reducedMotion, seed: 3 });
burst.play();
}

Under reduced motion play() draws that frame and settles at once, so destroying the burst as soon as play() settles would take the frame away with it.