library / interactive / waveform-pulse
live - 60fps 1920 x 1080
waveform-pulse.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Waveform Pulse</title>
<style>
    :root { --bg-color: #05060f; --bar-1: #22d3ee; --bar-2: #a855f7; }
    * { margin: 0; padding: 0; }
    body { height: 100vh; background: var(--bg-color); overflow: hidden; }
    canvas { display: block; }
</style>
</head>
<body>
    <canvas id="c"></canvas>
<script>
    const cv = document.getElementById('c'), ctx = cv.getContext('2d');
    const css = getComputedStyle(document.documentElement);
    const bg = (css.getPropertyValue('--bg-color') || '#05060f').trim();
    function hex(h){h=(h||'').trim().replace('#','');if(h.length===3)h=h.split('').map(c=>c+c).join('');const n=parseInt(h,16);return[(n>>16)&255,(n>>8)&255,n&255];}
    const b1 = hex(css.getPropertyValue('--bar-1') || '#22d3ee');
    const b2 = hex(css.getPropertyValue('--bar-2') || '#a855f7');
    let w, h, cx, cy, R;
    const BARS = 96; const amp = new Float32Array(BARS);
    let boost = 0;
    function build() { w = cv.width = innerWidth; h = cv.height = innerHeight; cx = w / 2; cy = h / 2; R = Math.min(w, h) * 0.17; }
    addEventListener('resize', build);
    addEventListener('mousemove', () => { boost = 1; });
    build();
    // pseudo-spectrum: layered sines per bar so it reads like live audio
    function spectrum(i, t) {
        const f = i / BARS;
        let v = Math.sin(f * 9 + t * 3) * 0.5 + Math.sin(f * 23 - t * 5) * 0.3 + Math.sin(f * 4 + t * 2) * 0.5;
        v = Math.abs(v) * (0.6 + 0.4 * Math.sin(f * Math.PI));   // rolloff at edges
        return v;
    }
    function step(t) {
        ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
        boost *= 0.94;
        ctx.lineCap = 'round';
        for (let i = 0; i < BARS; i++) {
            const target = spectrum(i, t) * (1 + boost * 1.2);
            amp[i] += (target - amp[i]) * 0.25;
            const len = R * (0.15 + amp[i] * 1.35);
            const a = i / BARS * Math.PI * 2 - Math.PI / 2 + t * 0.05;
            const ix = cx + Math.cos(a) * R, iy = cy + Math.sin(a) * R;
            const ox = cx + Math.cos(a) * (R + len), oy = cy + Math.sin(a) * (R + len);
            const mix = amp[i];
            ctx.strokeStyle = `rgb(${b1[0]+(b2[0]-b1[0])*mix|0},${b1[1]+(b2[1]-b1[1])*mix|0},${b1[2]+(b2[2]-b1[2])*mix|0})`;
            ctx.lineWidth = 3;
            ctx.beginPath(); ctx.moveTo(ix, iy); ctx.lineTo(ox, oy); ctx.stroke();
        }
        // inner ring
        ctx.strokeStyle = `rgba(${b1[0]},${b1[1]},${b1[2]},0.5)`; ctx.lineWidth = 1.5;
        ctx.beginPath(); ctx.arc(cx, cy, R - 4, 0, Math.PI * 2); ctx.stroke();
    }
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) step(1.0);
    else { let t = 0; (function loop(){ t += 0.02; step(t); requestAnimationFrame(loop); })(); }
</script>
</body>
</html>

About this animation

A radial audio visualizer of bars pulsing around a ring, breathing and swelling as if driven by music — and it reacts to your mouse.

Source Code
Copied to clipboard