AtmosphereFree

Frequency Bars

Twenty DOM bars animated by CSS transitions simulate an audio equalizer — indigo-to-violet gradient, bottom-aligned flex, zero canvas, zero dependencies.

JSClassic
Hover or click the scene to interact

This effect is free — the Atmosphere category contains 57 effects total, including 10 free. Effect.Labs has 811 vanilla effects. Explore the category →

Usage examples

① Hero / Landing — Audio player or podcast app

WhenPlayback is active on the landing page: the hero shows the track title, artist, and a Subscribe CTA while the equalizer animates in the background.
WhyThe equalizer provides immediate visual proof that audio is working, before the user even clicks play — it reduces doubt and shortens time to trial decision.
Settings20 bars (barCount = 20), container height 120 px, gap 4 px, brand gradient. The native requestAnimationFrame loop delivers smooth rendering with no extra configuration.

② Product component — Mic level widget in a video conferencing app

WhenThe audio settings card shows real-time microphone level — typically in a conference dashboard or streaming app.
WhyThe DOM-based visualization is lighter than a canvas inside a dense UI; CSS transitions absorb frequent updates without visible reflow.
Settings12 bars (barCount = 12), height 80 px, width 6 px, gap 3 px. Signal fed by AnalyserNode.getByteFrequencyData() at 60 FPS.
Free preview

Hear the difference before you buy

30-second preview — no account required

Intro · Effect.Labs
0:00 / 0:30

③ Conversion micro-interaction — Audio preview on a SaaS landing page

WhenThe user hovers or clicks a 'Listen to preview' button — bars animate to signal audio is active, then a 'Try for free' CTA appears.
WhyImmediate visual feedback (bars move on click) reduces perceived latency before audio actually starts, and the 'live sound = working product' association builds trust before conversion.
Settings16 bars, height 60 px, green gradient (#10b981#34d399) to echo the CTA button color. Transition at 0.08s for a snappier feel.

How it works

The #frequencyBars container is a flexbox with align-items: flex-end: when a .bar's height grows, the bar rises upward — the growth axis is inverted relative to the DOM's natural reading direction. The JS generates .bar elements using the barCount constant (20 by default); flexbox distributes them automatically with a 4 px gap and centers them horizontally.

The loop starts on page load with an initial requestAnimationFrame(animate) call inside the IIFE. On each frame, getSimulatedAudio(barCount, time) computes 20 values in [0, 1]; animate then applies bar.style.height = (10 + data[i] × 100) + 'px' to each .bar. The CSS transition: height 0.05s visually smooths the jumps between frames — the browser handles interpolation with no extra per-frame JS computation.

getSimulatedAudio(count, time) simulates an audio spectrum without a microphone or audio file. For each bar i, an individual frequency is computed (freq = 1 + i × 0.5) and mixed across three components: sin(t × freq × 0.002) × 0.5 (slow wave), sin(t × freq × 0.003 + i) × 0.3 (bar-shifted wave), Math.random() × 0.2 (noise). The absolute value is clamped to [0, 1] and converted to a height: minimum 10 px (silence) · maximum 110 px (peak). For a real audio signal, replace the getSimulatedAudio() call with values from AnalyserNode.getByteFrequencyData() mapped to [0, 1].

The effect targets a single id="frequencyBars" — designed for one instance per page. For multiple simultaneous instances, pass the element as a parameter to the animation logic instead of calling getElementById directly inside the loop.

Accessibility

  • prefers-reduced-motion handled: the IIFE reads window.matchMedia('(prefers-reduced-motion: reduce)').matches on startup. If reduced, the if (!reduceMotion) requestAnimationFrame(animate) condition does not restart the loop after the first frame — bars stay frozen at their last computed position.
  • No ARIA attributes on #frequencyBars or the .bar elements. For decorative use, add aria-hidden="true" to the container; for informative use (live audio signal display), add role="img" and aria-label.
  • The <div class="bar"> elements are not focusable and intercept no keyboard events — no focus-trap risk.
  • The indigo-to-violet gradient (#6366f1#d946ef) on a dark background: decorative graphic component, WCAG 1.4.11 (Non-text Contrast ≥ 3:1) met on #0a0a0f.
  • The effect is purely visual — no critical information is conveyed through the visualization. Screen readers don't need to read its state.

Browser compatibility

Requires CSS Transitions and Flexbox — supported in all browsers since 2015. Zero external dependencies.

Chrome 80+✓ Full
Firefox 75+✓ Full
Safari 13+✓ Full
Edge 80+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

Without CSS Transitions, bars jump instantly to their target height with no interpolation — the effect is still legible, just choppy. Without Flexbox (IE 9 and older), the layout breaks; no fallback is provided in the code.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="frequency-bars" id="frequencyBars">
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
  <div class="bar" style="height: 10px;">
  </div>
</div>
CSS
.frequency-bars  {
   display: flex;
   align-items: flex-end;
   justify-content: center;
   gap: 4px;
   height: 120px;
   }

.frequency-bars .bar  {
   width: 8px;
   background: linear-gradient(to top, rgb(99, 102, 241), rgb(217, 70, 239));
   border-radius: 4px 4px 0px 0px;
   transition: height 0.05s;
   }
JavaScript (fx-0515)
(function () {
  const frequencyBars = document.getElementById('frequencyBars');
  if (!frequencyBars) return;
  const barCount = 20;
  for (let i = 0; i < barCount; i++) {
    const bar = document.createElement('div');
    bar.className = 'bar';
    bar.style.height = '10px';
    frequencyBars.appendChild(bar);
  }
  // Audio simulé (pas de micro ni de fichier) : superposition de sinus + bruit
  function getSimulatedAudio(count, time, baseFreq = 1) {
    const values = [];
    for (let i = 0; i < count; i++) {
      const freq = baseFreq + i * 0.5;
      const value = Math.abs(
        Math.sin(time * freq * 0.002) * 0.5 +
        Math.sin(time * freq * 0.003 + i) * 0.3 +
        Math.random() * 0.2
      );
      values.push(Math.min(1, value));
    }
    return values;
  }
  const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  function animate(time) {
    const bars = frequencyBars.querySelectorAll('.bar');
    const data = getSimulatedAudio(barCount, time);
    bars.forEach((bar, i) => { bar.style.height = (10 + data[i] * 100) + 'px'; });
    if (!reduceMotion) requestAnimationFrame(animate);
  }
  requestAnimationFrame(animate);
})();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
barCount (JS constant) 20 Number of bars created at startup. Increase to 32–64 for a dense spectrum; drop to 8–12 for a compact widget. Set before the initialization call.
.frequency-bars height 120px Maximum height a bar can reach. The CSS value controls the container — bars never exceed it.
.frequency-bars .bar width 8px Width of each bar in px. Reduce to 4–5 px for a dense spectrum; increase to 12–16 px for a chunky style.
.frequency-bars gap 4px Spacing between bars. Adjust proportionally to width: a width/gap ratio of ~2 keeps a balanced look.
.frequency-bars .bar background linear-gradient(to top, #6366f1, #d946ef) Gradient colors. Swap in your brand palette. The to top direction aligns the foot color (1) toward the head (2) — reverse for a different feel.
.frequency-bars .bar transition height 0.05s CSS interpolation speed. 0.05s (50 ms) = responsive. Increase to 0.12s for smoother, more musical motion; drop to 0s for an instant jump (technical visualizer).
.frequency-bars .bar border-radius 4px 4px 0 0 Corner rounding on bar tops. 4px 4px 0 0 = open capsule at the bottom. 4px (all sides) = floating capsule; 0 = strict rectangle.

FAQ

Is the audio signal simulated or real?

Simulated by default: the IIFE includes getSimulatedAudio(), which overlays sinusoids at staggered frequencies (freq × 0.002 and freq × 0.003) and adds random noise (× 0.2) to mimic a live spectrum without a microphone or audio file. To connect a real audio source, create an AnalyserNode, call analyser.getByteFrequencyData(dataArray) inside the requestAnimationFrame loop, and map the values [0–255] to [0, 1] as a drop-in replacement for getSimulatedAudio().

Can I display multiple instances on the same page?

Not directly: the JS targets getElementById('frequencyBars'), which returns a single element. For multiple instances, give each container a unique id (frequencyBars1, frequencyBars2…) and run the animation logic independently for each, passing the element as a variable rather than repeating getElementById inside the loop.

What's the performance impact?

Very low for 20 bars at 10–20 updates per second: CSS Transitions delegate interpolation to the browser's composite engine. Beyond 60 bars or at 60 FPS, watch for reflows — each bar.style.height update can trigger a layout recalculation. For dense or high-frequency configurations, switch to a 2D canvas (one render operation per frame).