Audio Visualizer
Web Audio API audio spectrum visualization drawn in canvas 2D, in bar, mirror, and radial styles, able to analyze a real audio source or drive a decorative animation from synthetic data.
This is a WebberUI Pro component
Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.
npx shadcn@latest add "https://webberui.com/r/audio-visualizer.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<AudioVisualizer />
Installation
npx shadcn@latest add "https://webberui.com/r/audio-visualizer.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/audio-visualizer.
Usage
With no audio source provided, the component is driven by procedurally synthesized data, which makes it a good fit for a decorative animation:
import { AudioVisualizer } from "@/components/ui/audio-visualizer";
<div className="h-40 overflow-hidden rounded-xl">
<AudioVisualizer variant="bars" />
</div>Analyzing a real media element
Pass the ref of an <audio> / <video> element to media and the component reads the live spectrum through the Web Audio API. The media's sound is played back through the audio graph, so it stays audible:
"use client";
import * as React from "react";
import { AudioVisualizer } from "@/components/ui/audio-visualizer";
export function Player() {
const audioRef = React.useRef<HTMLAudioElement>(null);
const [playing, setPlaying] = React.useState(false);
return (
<div>
<audio ref={audioRef} src="/track.mp3" />
<AudioVisualizer
variant="mirror"
media={audioRef}
active={playing}
color="#22d3ee"
className="h-40"
/>
<button
onClick={() => {
const el = audioRef.current;
if (!el) return;
if (playing) el.pause();
else void el.play();
setPlaying(!playing);
}}
>
Play / Pause
</button>
</div>
);
}Analyzing the microphone
Pass the MediaStream you get from getUserMedia to stream (the microphone is not played back, which avoids feedback):
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
<AudioVisualizer variant="radial" stream={stream} sensitivity={1.4} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "bars" | "mirror" | "radial" | "bars" | Visualization style: bars from the bottom, mirrored about a center line, or radial around a circle |
media | RefObject<HTMLMediaElement | null> | — | Ref of the <audio>/<video> to analyze; once provided, the real spectrum is analyzed |
stream | MediaStream | null | null | Supply an audio stream directly (the microphone, for example); takes precedence over media |
active | boolean | true | Whether the animation runs; at false it freezes into a still low-amplitude state |
bars | number | 48 | Number of frequency bands (rays for radial), clamped internally to 4–256 |
color | string | a theme-following neutral | Primary color; when not specified it follows currentColor (which switches with the light/dark theme) |
fftSize | number | 2048 | FFT window size; must be a power of two (32–32768) |
smoothing | number | 0.8 | Spectrum smoothing coefficient 0–1; the higher, the smoother and the slower to react |
sensitivity | number | 1 | Sensitivity multiplier, amplifying the overall amplitude |
label | string | "音訊視覺化" (Audio visualization) | Accessible label applied as the container's aria-label |
className | string | — | Class appended to the outermost container |
How it works
- Two data-source modes. When
mediaorstreamis provided, anAnalyserNodeis created and the real spectrum is read each frame withgetByteFrequencyData, then grouped intobarsbands on an "approximately logarithmic" scale that emphasizes the low end; with no audio source it falls back to procedurally synthesized data (multi-layer sine oscillation plus beat pulses), so a demo environment can show it off without any external audio file - A single canvas 2D layer. All three styles are drawn on the same
<canvas>:barsrise from the bottom,mirrormirrors above and below a center line, andradialradiates outward around a circle; each frame is redrawn after aclearRect, with rounded corners from the nativeroundRect(falling back to hand-drawnarcTowhere it is unsupported) - MediaElementSource can only be created once. A given media element can only have a source node created for it once in its whole lifetime, and creating one again throws; the component caches the "context / source / analyser" audio graph in a
WeakMapand reuses it across remounts and re-renders, releasing it automatically once the element is collected - AudioContext lifecycle. A context created from a microphone
streamisclose()d on unmount; the audio graph for a media element is not closed, because it is cached for reuse. If the context issuspended(the browser requires a user gesture),resume()is attempted whileactive - The real canvas size is set from
devicePixelRatio(capped at 2) and then scaled withsetTransform, so Retina screens stay sharp; aResizeObserverwatches for container changes and redraws immediately - When
coloris not specified, the canvas'scurrentColoris read (resolved from atext-neutral-*class), and aMutationObserverwatches class changes on<html>so the color is re-read the moment the light/dark theme switches
Accessibility
- The container carries
role="img"and a customizablearia-label(「音訊視覺化」, Audio visualization, by default), while the inner<canvas>is markedaria-hiddenso the per-frame image is not announced - When the user has "reduce motion" enabled at the system level, the
requestAnimationFrameloop is disabled entirely and only a single still low-amplitude silhouette is rendered, with the DOM structure unchanged (no two-stage mount needed) - The visualization is a decorative mapping of sound the user is already hearing; make sure the actual playback controls (the play/pause button and so on) have complete keyboard and labelling support of their own
Custom Video Player
A video player with a custom control bar that fades out automatically during playback, supporting scrub-to-seek, volume, fullscreen, and keyboard operation.
Image Compare
Drag the divider in the middle to compare a before and an after layer, clipped with clip-path and following smoothly on a spring.