Circular Visualizer
32 CSS bars in a circle animated by two Math.sin oscillators plus noise — pure DOM, zero canvas, plug-in ready for a Web Audio AnalyserNode.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →
3 usage examples



How it works
The .circular-visualizer container is a 160 × 160 px square in position:relative. The JS creates 32 .bar elements positioned position:absolute; bottom:50%; left:50% with transform-origin: center bottom. Each bar gets translateX(-50%) rotate(i/32 × 360°): the translation centers the bar on its axis, the rotation points it outward. The result is 32 evenly-spaced radii around a shared center.
getSimulatedAudio(32, time, 0.5) generates a [0–1] value per bar using two Math.sin oscillators at distinct frequencies (baseFreq + i × 0.5 and × 1.5 of that), plus 20% Math.random(). The combination produces an organic signal where each bar has its own oscillation rhythm — no microphone permission or Web Audio API required.
On each requestAnimationFrame tick, animateCircularVisualizer(time) maps each simulated value to a height of 20 + value × 50 px (range 20–70 px) and rewrites bar.style.height and bar.style.transform. The CSS transition: height 0.05s smooths discrete per-frame jumps into fluid motion without any JS easing.
No prefers-reduced-motion handler is present: the rAF loop runs unconditionally. No cancelAnimationFrame or IntersectionObserver is built in — add these in your integration if the component is off-viewport.
Accessibility
- prefers-reduced-motion not handled: no detection in the code — the rAF runs continuously regardless of OS preference. Add
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;beforerequestAnimationFrame(animate). - The
.circular-visualizercontainer has noroleoraria-labelin the base code — decorative by default. Addrole="img" aria-label="Circular audio visualizer"in your integration if the element is meaningful. - Bars are pure decorative divs with no text content: screen readers will not announce them (intended behavior).
- No keyboard interaction or focus: the effect is passive, no mouse or keyboard events are managed.
- The gradient
#6366f1 → #06b6d4on a#0a0a0fbackground achieves a luminance ratio of ~4.2:1 — acceptable WCAG AA for a decorative element.
Browser compatibility
Pure DOM + CSS transforms + requestAnimationFrame — no canvas, no external dependency. Works anywhere CSS 3D transforms and rAF are available.
If JS is disabled, the <code>.circular-visualizer</code> container renders empty (160 × 160 px, transparent background) — no errors, the page stays functional. If the embedded HTML is used, the 32 pre-rendered bars stay static at their initial snapshot heights.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="demo-preview">
<div class="circular-visualizer"
id="circularVisualizer"
role="img"
aria-label="Circular audio visualizer">
<!-- 32 .bar elements injected by JS -->
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| .circular-visualizer { width / height } | 160px | Container size = apparent circle radius. Change both dimensions together to scale up or down. |
| .circular-visualizer .bar { background } | linear-gradient(to top, #6366f1, #06b6d4) | Bar gradient colors, from base (dark color) to tip (bright color). Replace with your brand palette. |
| .circular-visualizer .bar { width } | 4px | Bar thickness in px. Increase to 6–8 px for few bars (≤ 16), reduce to 2–3 px for many bars (≥ 48). |
| .circular-visualizer .bar { transition } | height 0.05s | Smoothing delay between frames. Reduce to 0.02s for a snappier feel, increase to 0.15s for a more liquid motion. |
| circularBarCount = 32 | 32 | Number of bars in the circle. Between 16 (wide, spaced) and 64 (dense crown). Always update bar width accordingly. |
| 20 + data[i] * 50 in animateCircularVisualizer | min 20 px, max 70 px | Minimum height (20) and amplitude (50) per bar in px. Increase amplitude to 80 for a more dramatic effect, lower the minimum to 10 for a subtler idle state. |
| getSimulatedAudio(32, time, 0.5) — 3rd argument | 0.5 | Base oscillator frequency. Increase to 1.5 for faster animation, reduce to 0.2 for slow, steady motion. |
FAQ
getSimulatedAudio() (two Math.sin() per bar plus 20% random noise). No microphone permission is requested. To connect a real audio stream, replace the getSimulatedAudio() call with a read from analyserNode.getByteFrequencyData(data) divided by 255 to normalise between 0 and 1.IntersectionObserver: call requestAnimationFrame(animate) on viewport entry and cancelAnimationFrame(rafId) on exit. The Balloon Release effect (fx-0837) includes this IntersectionObserver natively as a reference.window.matchMedia('(prefers-reduced-motion: reduce)').matches before starting the loop. If the preference is active, render the bars once at a fixed height (e.g. 30 px) without launching a rAF.