Background
This example builds directly on
Tutorial 08: Audio Processing with the Web Audio API,
which introduced the AudioContext, node graph model, and the
AnalyserNode. Here, all of those pieces are combined into a
self-contained visualizer: a <canvas> element that draws
live frequency bars while the audio plays, and stops drawing when playback
pauses or ends.
Browser autoplay policy requires audio to be started by an explicit user
gesture — a button click. The AudioContext is therefore created
(or resumed) only on click, never on page load. The
MediaElementSource is connected to the graph exactly once, guarded
by a flag so the same audio element is not re-routed on subsequent clicks.
Live demo
Click Play & Visualize to start the audio and watch the frequency bars animate. Click Pause to stop both. If your system preferences have reduced motion enabled, a single static bar snapshot will be drawn instead of an animation loop.
The Web Audio graph
The key to the visualizer is the node graph: audio flows from a
MediaElementSource through an AnalyserNode and on
to the speakers (ctx.destination). The analyser sits in the
middle and can inspect the signal without affecting it.
Call createMediaElementSource only once.
Calling it a second time on the same <audio> element
throws an InvalidStateError. The connected flag
ensures the graph is built exactly once, no matter how many times the user
clicks Play.
The draw loop
Each animation frame, getByteFrequencyData() fills a
Uint8Array with the current amplitude of each frequency bin
(values 0–255). The loop draws a bar for each bin whose height is
proportional to its amplitude, then requests the next frame — but only
while the audio is still playing.
Why stop the loop on pause? Continuing to call
requestAnimationFrame while paused wastes CPU and battery.
The loop checks audio.paused every frame and exits naturally —
no extra bookkeeping needed.
Reduced-motion accessibility
Some users configure their operating system to request reduced motion —
for example, to avoid triggering vestibular disorders. The
prefers-reduced-motion media query exposes this preference to CSS
and JavaScript. When it matches, the visualizer draws exactly one static
frame and skips the requestAnimationFrame loop entirely.
You can test this in Chrome DevTools: open the Rendering panel (via the three-dot menu → More tools → Rendering) and check Emulate CSS media feature prefers-reduced-motion: reduce. Click Play on the demo above — you should see a single frozen frame instead of the animated bars.