Skip to content

Examples

Each example below is complete: copy its JavaScript and its HTML with the buttons on the code, and it runs with @motly/gsap and gsap installed. With script tags instead, drop the imports and use Motly.rand and Motly.each; the script build registers itself.

Five examples that each show something only a GSAP plugin can do. They are also on CodePen, loading everything from a CDN.

Timeline sequencing: the heart squashes, then a ring, a pop and sparks, each placed with GSAP’s position parameter. Unliking mid-burst reverts the timeline, which clears it. Open on CodePen.

import { each, Motly, rand } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const ring = {
kind: 'circle',
radius: [10, 52],
fill: 'none',
stroke: ['#ff4d6d', '#ffb3c1'],
strokeWidth: [12, 0],
duration: 0.45,
restAt: 0.3,
};
const sparks = {
kind: 'burst',
count: 14,
radius: [30, 80],
restAt: 0.4,
children: {
kind: 'circle',
radius: [rand(3, 6), 0],
fill: each(['#ff4d6d', '#ffd166', '#8338ec', '#3a86ff']),
duration: rand(0.5, 0.8),
},
};
const heart = document.querySelector('.heart');
let like;
heart.addEventListener('click', () => {
const liked = heart.classList.toggle('liked');
heart.setAttribute('aria-pressed', String(liked));
// revert() clears a burst mid-flight and puts the scale back; kill() would leave it drawn.
like?.revert();
if (!liked) return;
like = gsap
.timeline()
.to(heart, { scale: 0.6, duration: 0.12, ease: 'power2.in' })
.shape(heart, { spec: ring })
.to(heart, { scale: 1, duration: 0.6, ease: 'elastic.out(1.2, 0.4)' }, '<')
.burst(heart, { spec: sparks }, '<0.05');
});

Three bursts in one timeline: from the button, then from both sides of the screen at once. Open on CodePen.

import { each, Motly, rand } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const palette = ['deeppink', 'gold', 'cyan', 'mediumpurple', 'limegreen'];
const piece = { fill: each(palette), stroke: each(palette), duration: rand(0.9, 1.5) };
/** Confetti thrown `reach` pixels out: stars, squares, dots and streamers. */
const confetti = (count, reach) => ({
kind: 'burst',
count,
radius: [0, reach],
easing: 'ease-out',
restAt: 0.35,
children: each([
{ kind: 'star', points: 5, radius: [rand(6, 12), 0], angle: [0, rand(-270, 270)], ...piece },
{ kind: 'polygon', points: 4, radius: [rand(4, 9), 0], angle: [0, rand(-180, 180)], ...piece },
{ kind: 'circle', radius: [rand(3, 7), 0], ...piece },
{ kind: 'line', radius: [rand(5, 10), 0], angle: [0, rand(-360, 360)], ...piece },
]),
});
const button = document.querySelector('.celebrate');
button.addEventListener('click', () => {
// Points are viewport pixels, read when the click happens.
const left = { x: innerWidth * 0.15, y: innerHeight * 0.3 };
const right = { x: innerWidth * 0.85, y: innerHeight * 0.3 };
gsap
.timeline()
.to(button, { scale: 0.92, duration: 0.1, yoyo: true, repeat: 1 })
.burst(button, { spec: confetti(28, 140) }, '<')
// 0.25 s after the button's burst starts, both sides at once.
.burst(left, { spec: confetti(40, 220) }, '<0.25')
.burst(right, { spec: confetti(40, 220) }, '<');
});

ScrollTrigger scrubs a timeline of rockets and shells, back and forth. The shells are painted in the pinned section, so they scroll with it. Scroll inside the example. Open on CodePen.

import { each, Motly, rand } from '@motly/gsap';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(Motly, ScrollTrigger);
// A shell of 10 bursts of 24 sparks: 240 Elements, so 'auto' paints it on a canvas.
const shell = (colors) => ({
kind: 'burst',
count: 10,
radius: [0, 60],
restAt: 0.4,
children: {
kind: 'burst',
count: 24,
radius: [0, rand(40, 70)],
children: {
kind: 'circle',
radius: [rand(1.5, 3.5), 0],
fill: each(colors),
duration: rand(0.9, 1.5),
},
},
});
const [first, second, third] = gsap.utils.toArray('.rocket');
// The section is the container: bursts scroll with it and are clipped by it.
const inSky = { container: '.sky' };
gsap
.timeline({
scrollTrigger: { trigger: '.sky', start: 'top top', end: '+=2500', scrub: 1, pin: true },
})
// Each rocket rises, then its shell opens where it stopped as the rocket fades.
.to(first, { y: '-65vh', duration: 1, ease: 'power2.out' })
.burst(first, { spec: shell(['gold', 'orangered']), ...inSky })
.to(first, { opacity: 0, duration: 0.2 }, '<')
.to(second, { y: '-75vh', duration: 1, ease: 'power2.out' }, '<0.3')
.burst(second, { spec: shell(['deepskyblue', 'white']), ...inSky })
.to(second, { opacity: 0, duration: 0.2 }, '<')
.to(third, { y: '-55vh', duration: 1, ease: 'power2.out' }, '<0.2')
.burst(third, { spec: shell(['deeppink', 'mediumpurple', 'gold']), ...inSky })
.to(third, { opacity: 0, duration: 0.2 }, '<');

Every click bursts from its own point. Open on CodePen.

import { each, Motly, rand } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const sparkle = {
kind: 'burst',
count: 9,
radius: [0, rand(40, 70)],
stagger: 0.02,
restAt: 0.4,
children: {
kind: 'star',
points: 4,
innerRadius: 0.3,
radius: [rand(6, 11), 0],
angle: [0, rand(-90, 90)],
fill: each(['#ffd60a', '#ffffff', '#90e0ef']),
duration: rand(0.4, 0.7),
},
};
// A point is { x, y } in viewport pixels, exactly what clientX and clientY give.
addEventListener('pointerdown', (event) => {
gsap.effects.burst({ x: event.clientX, y: event.clientY }, { spec: sparkle });
});

Clicking again mid-ripple reverses the timeline, and the ripple runs back into the button. Open on CodePen.

import { each, Motly } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const ripple = {
kind: 'burst',
count: 16,
radius: [0, 110],
stagger: { each: 0.04, easing: 'ease-in' },
restAt: 0.5,
children: {
kind: 'star',
points: 4,
innerRadius: 0.35,
radius: [14, 0],
angle: [0, 90],
fill: each(['#ef6f6c', '#f7b267']),
duration: 0.7,
},
};
const toggle = document.querySelector('.toggle');
const tl = gsap
.timeline({ paused: true })
.to('.toggle span', { rotate: 135, duration: 0.5, ease: 'back.out(2)' })
.burst(toggle, { spec: ripple }, 0);
toggle.addEventListener('click', () => {
const opening = tl.progress() === 0 || tl.reversed();
opening ? tl.play() : tl.reverse();
toggle.setAttribute('aria-expanded', String(opening));
toggle.setAttribute('aria-label', opening ? 'Close' : 'Open');
});

A burst around a card as the pointer enters it. Reverting the last one first keeps quick hovers from piling up.

import { each, Motly, rand } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const sparkle = {
kind: 'burst',
count: 8,
radius: [40, rand(90, 120)],
restAt: 0.3,
children: {
kind: 'star',
points: 4,
innerRadius: 0.3,
radius: [rand(4, 8), 0],
fill: each(['#ffd60a', '#ffffff']),
duration: rand(0.5, 0.8),
},
};
const card = document.querySelector('.card');
let hover;
function sparkleCard() {
// One sparkle at a time: a new hover replaces the last one instead of piling up.
hover?.revert();
hover = gsap.effects.burst(card, { spec: sparkle });
}
card.addEventListener('pointerenter', sparkleCard);
card.addEventListener('focus', sparkleCard);

A ring and a few dots confirm a click, and the label changes back once the timeline reaches 1.5 seconds.

import { Motly, rand } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const ring = {
kind: 'circle',
radius: [30, 70],
fill: 'none',
stroke: '#06d6a0',
strokeWidth: [6, 0],
duration: 0.5,
restAt: 0.2,
};
const dots = {
kind: 'burst',
count: 6,
radius: [40, 80],
restAt: 0.3,
children: { kind: 'circle', radius: [rand(2, 4), 0], fill: '#06d6a0', duration: 0.5 },
};
const button = document.querySelector('button');
button.addEventListener('click', () => {
button.textContent = 'Copied';
gsap
.timeline()
.shape(button, { spec: ring })
.burst(button, { spec: dots }, '<0.05')
.call(
() => {
button.textContent = 'Copy link';
},
[],
1.5,
);
});

A ring that repeats until the notification is read. revert() stops it and clears it mid-flight.

import { Motly } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const ping = {
kind: 'circle',
radius: [20, 60],
fill: 'none',
stroke: '#ef476f',
strokeWidth: [6, 0],
duration: 1,
restAt: 0.2,
};
// An unread notification pings until it is read.
const pinging = gsap.effects.shape('.bell', { spec: ping, repeat: -1, repeatDelay: 0.6 });
document.querySelector('button').addEventListener('click', (event) => {
// revert() clears the ring mid-flight; the tween leaves nothing drawn.
pinging.revert();
event.currentTarget.disabled = true;
event.currentTarget.textContent = 'All read';
});

A path Curve copied from a design tool: it overshoots to 120%, falls back to 90% and settles.

import { Motly } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
// An SVG path curve, as drawn in a design tool: overshoot to 120%, fall back to 90%, settle.
const jelly = 'M0,100 C15,40 25,-20 40,-20 C55,-20 60,10 70,10 C80,10 90,0 100,0';
const spec = {
kind: 'polygon',
points: 6,
radius: [0, 70],
angle: [-30, 0],
fill: '#8338ec',
easing: { radius: jelly, default: 'ease-out' },
duration: 1.2,
};
gsap.effects.shape('.origin', { spec, repeat: -1, repeatDelay: 0.8 });

Mixed pieces, each wrapped in a Swirl so it wriggles outward, with neighbours curling opposite ways.

import { each, Motly, rand } from '@motly/gsap';
import { gsap } from 'gsap';
gsap.registerPlugin(Motly);
const palette = ['#ffd166', '#ef476f', '#06d6a0', '#118ab2'];
const piece = { fill: each(palette), angle: [0, rand(-360, 360)], duration: rand(0.9, 1.4) };
// Each piece is wrapped in a Swirl, so it wriggles outward; neighbours curl opposite ways.
const swirl = (child) => ({
kind: 'swirl',
size: rand(20, 40),
frequency: rand(1, 2),
direction: each([1, -1]),
child,
});
const spec = {
kind: 'burst',
count: 24,
radius: [0, rand(120, 160)],
easing: 'ease-out',
restAt: 0.35,
// A Swirl's child is one Spec, so each() hands out whole Swirls, one per kind of piece.
children: each([
swirl({ kind: 'polygon', points: 4, radius: [rand(4, 7), 0], ...piece }),
swirl({ kind: 'star', points: 5, radius: [rand(5, 9), 0], ...piece }),
swirl({ kind: 'circle', radius: [rand(3, 5), 0], ...piece }),
]),
};
const button = document.querySelector('button');
button.addEventListener('click', () => gsap.effects.burst(button, { spec }));