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;
}