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.
Install
Section titled “Install”npm install @motly/coreA first burst
Section titled “A first burst”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();<svg id="stage" aria-hidden="true" width="300" height="300"></svg>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.
Renderers
Section titled “Renderers”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();<canvas id="stage"></canvas>
<style> canvas { width: 360px; height: 300px; }</style>Reduced motion
Section titled “Reduced motion”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();}<main> <figure> <svg id="moving" aria-hidden="true" width="220" height="220"></svg> <figcaption>reducedMotion: 'never'</figcaption> </figure> <figure> <svg id="resting" aria-hidden="true" width="220" height="220"></svg> <figcaption>reducedMotion: 'always'</figcaption> </figure></main>
<style> main { display: flex; gap: 2rem; } figure { margin: 0; text-align: center; } figcaption { opacity: 0.6; font: 0.9rem ui-monospace, monospace; }</style>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.