MOTION
DESIGN

BUILT BY ANGELA CLEMONS · CSS · SVG · CANVAS · JS

// EXAMPLES

Six Live Demos

Six animations I built in pure CSS and vanilla JS, no libraries, no frameworks. Hover the cards to see the interaction states.

SVG Morphing
// CSS PATH ANIMATION · @keyframes d:
Particle Burst
// CANVAS 2D · CLICK TO TRIGGER
Terminal Typewriter
// JS setInterval · CHARACTER QUEUE
Fill Button
// CSS scaleY TRANSFORM · OVERFLOW HIDDEN
0
HOURS
:
0
MINS
:
0
SECS
Number Rollup
// JS requestAnimationFrame · EASING
CSS Orbit System
// CSS ANIMATION · COUNTER-ROTATE

// SOURCE

The Code Behind It

Tap a tab to inspect the technique. All pure web platform - no build tools required.

/* SVG path morphing via CSS @keyframes */ /* The 'd' property animates between paths */ @keyframes morphBlob { 0%, 100% { d: path("M60,20 C85,5 95,30 85,55 C75,80 45,90 25,75 C5,60 5,35 20,20 C35,5 35,35 60,20Z"); } 50% { d: path("M65,15 C92,10 100,40 88,62 C76,84 42,95 20,78 C-2,61 2,28 18,14 C34,0 38,20 65,15Z"); } } /* Apply to the SVG <path> element */ .morph-blob { animation: morphBlob 6s ease-in-out infinite; fill: url(#gradient); } /* Key rule: paths must have same number of points */
// Canvas particle burst on click const particles = []; function burst(x, y) { for (let i = 0; i < 40; i++) { const angle = (i / 40) * Math.PI * 2; const speed = Math.random() * 4 + 1; particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 1.0, size: Math.random() * 3 + 1, color: Math.random() > 0.5 ? '#00f5ff' : '#ff00aa' }); } } function tick() { ctx.clearRect(0, 0, w, h); for (const p of particles) { p.x += p.vx; p.y += p.vy; p.vy += 0.08; // gravity p.life -= 0.022; ctx.globalAlpha = p.life; ctx.fillRect(p.x, p.y, p.size, p.size); } requestAnimationFrame(tick); }
// Typewriter with multiple queued lines const lines = [ '> SYSTEM BOOT', '> LOADING ASSETS...', '> ALL SYSTEMS NOMINAL', '> READY_' ]; async function typeLine(el, text, speed = 42) { return new Promise(resolve => { let i = 0; const t = setInterval(() => { if (i >= text.length) { clearInterval(t); resolve(); return; } el.textContent = text.slice(0, ++i); }, speed); }); } async function runAll() { for (const [i, text] of lines.entries()) { await typeLine(els[i], text); await delay(300); // pause between lines } }
/* CSS-only orbital system */ /* Ring spins; dot counter-rotates to stay upright */ .orbit-ring { animation: spinRing 4s linear infinite; transform-origin: center; } @keyframes spinRing { to { transform: translate(-50%, -50%) rotate(360deg); } } /* Second ring goes the other direction */ .orbit-ring.r2 { animation-direction: reverse; animation-duration: 7s; } /* Dot positioned at top of ring */ .orbit-dot { position: absolute; top: -4px; left: 50%; margin-left: -4px; }