Examples

Organized by what you're building, not by how it's drawn — find your use case, copy the demo.

Fundamentals

Core interactions you can remix immediately.

Basic wave

Single wave with default gradient stroke.

Layered waves

Stack multiple waves for depth and parallax.

Pointer reactive

Pointer movement shapes amplitude and wavelength.

Dynamic management

Programmatic add/remove of wave layers.

Performance mode

Low-memory settings with pixel-ratio caps.

Custom easing

Swap easing curves for cinematic motion.

Pause and resume

Explicit control over the render loop.

Backgrounds & decorative patterns

What you'll reach for behind a hero, a card, or a panel — texture and motion that doesn't compete with your content.

Pulse matrix

Grid-based interference with tempo-driven color.

// Grid interference with tempo-driven color
for (let y = 0; y <= rows; y++) {
  for (let x = 0; x <= columns; x++) {
    const value = Math.sin(x * freq + t) * Math.cos(y * freq + t);
    ctx.arc(x * spacingX, y * spacingY, radius * value, 0, TWO_PI);
  }
}

DNA helix

Two counterphase strands with diagonal drift.

// Counterphase strands with diagonal drift
for (let y = 0; y <= height; y += 6) {
  const xOffset = Math.sin(y * freq + time) * amplitude;
  ctx.moveTo(centerX + xOffset, y);
  ctx.lineTo(centerX - xOffset, y);
}

Fluid column

Vertical flow with volumetric thickness.

// Vertical flow with volumetric thickness
for (let y = 0; y <= height; y += 6) {
  const offset = Math.sin(y * freq + time + col * 0.6) * amp;
  ctx.lineTo(columnX + offset, y);
}

Diagonal rain

Angled strokes with tempo-locked shimmer.

// Angled strokes with tempo-locked shimmer
for (let i = 0; i < count; i++) {
  const drift = Math.sin(time + i * freq) * amplitude;
  ctx.moveTo(baseX + drift, baseY);
  ctx.lineTo(baseX + drift + 24, baseY + 38);
}

Lissajous orbit

Parametric loops synced to time signature.

// Parametric loops synced to time
for (let i = 0; i <= 240; i++) {
  const t = (i / 240) * TWO_PI;
  const x = cx + Math.sin(t * freq + time) * amp;
  const y = cy + Math.sin(t * (freq + 1) + phase) * amp;
}

Waveform terrain

Layered horizons with depth-shifted phases.

// Layered horizons with depth-shifted phases
for (let layer = 0; layer < 8; layer++) {
  for (let x = 0; x <= width; x += 6) {
    const y = baseY + Math.sin(x * freq + phase) * amp;
    ctx.lineTo(x, y);
  }
}

Radial bloom

Outward pulses that peak on the downbeat.

// Outward pulses on the downbeat
for (let r = 18; r < radiusMax; r += 12) {
  const offset = Math.sin(r * freq + time) * pulse;
  ctx.arc(cx, cy, r + offset, 0, TWO_PI);
}

Gradient fill

Area under the wave filled with a linear gradient.

// Area fill with linear gradient
const grad = ctx.createLinearGradient(0, top, 0, height);
for (let x = 0; x <= width; x += 4) {
  ctx.lineTo(x, centerY + Math.sin(x * freq + t) * amp);
}
ctx.fillStyle = grad;
ctx.fill();

Audio visualizer

Simulated volume-driven multi-wave display.

// Volume-driven multi-wave display
waveConfigs.forEach((cfg) => {
  for (let x = 0; x <= width; x += 4) {
    const y = centerY + Math.sin(x * cfg.freq + t * cfg.speed) * amp;
    ctx.lineTo(x, y);
  }
});

Vertical wave

Rotated orientation for liquid-rising effects.

// Rotated orientation for liquid-rising effects
for (let y = 0; y <= height; y += 4) {
  const x = centerX + Math.sin(y * freq + time) * amp;
  ctx.lineTo(x, y);
}

Dash array

Dotted and dashed wave lines via setLineDash.

// Dotted and dashed wave lines
ctx.setLineDash([12, 6]);
for (let x = 0; x <= width; x += 4) {
  const y = centerY + Math.sin(x * freq + time) * amp;
  ctx.lineTo(x, y);
}

Variable width

Pulsing line thickness synchronized to tempo.

// Pulsing line thickness per segment
for (let x = 0; x < width; x += 6) {
  const thickness = 1 + Math.abs(Math.sin(x * 0.01 + t * 2)) * 4;
  ctx.lineWidth = thickness;
  ctx.lineTo(x, centerY + Math.sin(x * freq + t) * amp);
}

Compositing glow

Screen blending for neon glow effects.

// Screen blending for neon glow
ctx.globalCompositeOperation = "screen";
colors.forEach((cfg) => {
  ctx.shadowBlur = 20;
  ctx.lineTo(x, centerY + Math.sin(x * freq + t + cfg.offset) * amp);
});

Zen mode

Ultra-slow motion for maximum smoothness.

// Ultra-slow motion sine
for (let x = 0; x <= width; x += 6) {
  const y = centerY + Math.sin(x * freq + time * 0.15) * amp;
  ctx.lineTo(x, y);
}

Motion lab

Click, drag, or speak into these — motion that responds to you, not just to time.

String physics

Tap to pluck. Damped amplitude returns to rest.

Tip: click the line to excite it.

// Tap to pluck, damped amplitude returns to rest
canvas.addEventListener("pointerdown", () => { state.amplitude = 100; });
state.amplitude *= 0.95; // decay each frame
const y = centerY + Math.sin(x * 0.02 + phase) * state.amplitude * 0.02;

Audio spectrogram

64 wave forest mapped across bass to treble.

Idle
// 64-band frequency display
analyser.getByteFrequencyData(data);
for (let i = 0; i < 64; i++) {
  const amp = data[i] / 255;
  ctx.moveTo(x, base - amp * height * 0.3);
  ctx.lineTo(x, base + amp * height * 0.3);
}

Lissajous explorer

Orbit knots with adjustable a:b ratios.

// Orbit knots with adjustable a:b ratios
for (let i = 0; i <= 320; i++) {
  const t = (i / 320) * TWO_PI;
  const x = cx + ampX * Math.sin(a * t + delta + time);
  const y = cy + ampY * Math.sin(b * t + time);
}

Feedback loops

Slow modulator controls a fast carrier.

// Slow modulator controls a fast carrier
const mod = Math.sin(time * 0.8);
for (let x = 0; x <= width; x += 4) {
  const carrier = Math.sin(x * carrierFreq + time * 1.6);
  const y = centerY + carrier * baseAmp * mod;
}

Rhythm & signal

Biometric, voice, and looping waveforms — the shapes a heartbeat monitor, a voice message, a listening indicator, an equalizer, or a seamless loop actually draw.

Heartbeat monitor

A stylized EKG trace — P wave, QRS spike, T wave, repeat.

// Stylized EKG cycle: P wave, QRS spike, T wave
const beat = ekgWaveform((x / width + time / TWO_PI) % 1);
const y = centerY - beat * amplitude * 3;
ctx.lineTo(x, y);

Wave pattern

Three soft, overlapping layers for a calm background texture.

// Three overlapping layers, each with its own hue and offset
layers.forEach((layer, i) => {
  const y = centerY + Math.sin(x * freq + time + layer.offset) * amplitude;
  ctx.strokeStyle = `hsla(${layer.hue}deg 70% 65% / ${layer.alpha})`;
});

Voice line wave

A voice-message-style bar waveform, driven by a speech envelope.

// Bar height from a speech-like envelope, with occasional pauses
const envelope = Math.sin(t) * 0.5 + Math.sin(t * 2.3) * 0.3 + Math.sin(t * 0.6) * 0.4;
const gate = Math.max(0.08, (Math.sin(t * 0.2) + 1) * 0.5);
const barHeight = Math.abs(envelope) * amplitude * gate;

Search mic wave

Pulsing rings around a mic dot, live from AudioSync.

Idle
// Ring radius pulses with live mic energy
const audioSync = new AudioSync();
audioSync.connect(micStream);
const energy = audioSync.getMetrics().energy;
const radius = baseRadius + smoothedEnergy * 32;

Audio voice wave

A multi-band equalizer-style spectrum visualizer.

// 24 independent bands, each with its own phase offset
for (let i = 0; i < bands; i++) {
  const level = (Math.sin(t) * 0.5 + Math.sin(t * 1.7 + i) * 0.3 + 1) * 0.5;
  ctx.fillRect(x, baseline - barHeight, barWidth * 0.7, barHeight);
}

Wave loop

A closed, continuously wobbling ribbon — no start, no end.

// Closed loop with a sine-modulated radius
for (let i = 0; i <= points; i++) {
  const angle = (i / points) * TWO_PI;
  const radius = baseRadius + Math.sin(angle * lobes + time) * amplitude;
  ctx.lineTo(cx + Math.cos(angle) * radius, cy + Math.sin(angle) * radius);
}
ctx.closePath();

Voice recorder

Record your voice, watch the live waveform, then play it back.

Idle
// Live waveform while recording, then play the captured clip back
const audioSync = new AudioSync();
audioSync.connect(micStream);
generator.syncToAudio(audioSync, { amplitude: { source: "energy", intensity: 1.5 } });
const recorder = new MediaRecorder(micStream);
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => { audioEl.src = URL.createObjectURL(new Blob(chunks)); };

Wave mixer

A DJ-style crossfade between two independent waves.

// Two waves, each scaled by its own volume and the crossfade blend
const ampA = 26 * volumeA * (1 - crossfade);
const ampB = 26 * volumeB * crossfade;
ctx.lineTo(x, centerY + Math.sin(percent * TWO_PI * 2 + time) * ampA);
ctx.lineTo(x, centerY + Math.sin(percent * TWO_PI * 3 + time * 1.3) * ampB);

Accessibility & theming

Two features that are on by default but easy to miss — see them actually change the animation, not just read about them.

Reduced motion aware

Toggle to preview how visitors with prefers-reduced-motion see this animation.

Idle
// Honored automatically — this just previews the effect on demand
const generator = new SineWaveGenerator({ el: canvas });
// generator.prefersReducedMotion is auto-detected from the OS;
// toggling it here simulates that state for this preview.
generator.prefersReducedMotion = true;

Adaptive light/dark gradient

The built-in gradient repaints for light or dark — no rebuild.

Idle
// "auto" follows prefers-color-scheme live by default
const generator = new SineWaveGenerator({ el: canvas, colorScheme: "auto" });
// Force a palette instead of following the system preference:
generator.colorScheme = "dark";
generator.resize();

Hero background presets

Four ready-to-copy scenes — pick one, drop it behind your header, done.

The Ocean

Horizontal layering with slow tides.

// Horizontal layering with slow tides
// Uses drawWaveformTerrain with 8 depth layers

The Helix

Vertical strands with parallax depth.

// Vertical strands with parallax depth
// Uses drawDNAHelix with counterphase strands

The Pulse

BPM-reactive ripples with color sync.

// BPM-reactive ripples with color sync
// Uses drawRadialBloom with downbeat emphasis

The Orbit

Lissajous curves with orbital drift.

// Lissajous curves with orbital drift
// Uses drawLissajousOrbit with phase shift

Generative & experimental art

For when you want something that feels alive, not just animated.

Interference grid

Two angled lattices creating moire shimmer.

// Two angled lattices creating shimmer
const drawGrid = (rotation) => {
  ctx.rotate(rotation);
  for (let x = -max; x <= width + max; x += spacing) {
    ctx.moveTo(x, -max);
    ctx.lineTo(x, height + max);
  }
};

Kinetic typography

Letterforms that breathe with the beat.

// Letterforms that breathe with the beat
"SINE WAVE".split("").forEach((char, i) => {
  const wave = Math.sin(time + i * freq) * amplitude;
  ctx.fillText(char, cx + i * 18, cy + wave);
});

Damped sine

Organic motion with exponential decay.

// Exponential decay envelope
const envelope = Math.exp(-3.2 * progress);
for (let x = 0; x <= width; x += 4) {
  const y = centerY + Math.sin(x * freq + time) * amp * envelope;
  ctx.lineTo(x, y);
}

Recursive sine

Nested oscillators for organic drift.

// Nested oscillators for organic drift
for (let x = 0; x <= width; x += 4) {
  const nested = Math.sin(time + x * baseFreq * 0.4) * 0.6 + 1;
  const y = centerY + Math.sin(x * (freq + mod * nested) + time) * amp;
  ctx.lineTo(x, y);
}