Bryan Hamilton / projects / eq-visualizer

bryanhamilton.info / open source / eq-visualizer

eq-visualizer "This page is about sound," in a few pixels.

The 8-bar equalizer from The DJ Calendar's artist cards, extracted as a standalone widget. The motion is pure CSS: eight bars, each with its own tempo and starting point, so it moves like audio and never looks like a metronome. There's no JavaScript in the animation at all, and visitors who ask for less motion get a calm, still meter instead.

  • Pure CSS animation
  • Optional 1 KB injector
  • Reduced-motion friendly
  • MIT license
In brief: eq-visualizer is an eight-bar equalizer widget whose motion is animated entirely with CSS. It can render with no JavaScript, offers an optional lightweight injector, supports per-instance styling, and settles to a static meter when reduced motion is preferred.

Playground

Turn the knobs. The markup below updates as you go.

On The DJ Calendar

The homepage card pattern: hover (or Tab to) the photo

Anyma

On the live site these bars are drawn from the real audio. See that version →

Now playinginline, 14px tall
Live setinside a badgeLIVE

The opinion underneath it

An equalizer next to a DJ's name is a promise: this page is about sound. Most sites make that promise with an autoplaying video or a heavy animated hero and call it sensory design. A small meter that moves like audio says the same thing in a few pixels of height, costs almost nothing to load, and never makes a sound until someone asks for one.

How the motion stays alive

Eight bars bouncing in unison look mechanical. The trick is that no two bars share a rhythm: each gets its own duration and a negative delay, so every bar starts the page already partway through its cycle. Because the durations don't divide evenly into each other, the combined pattern takes a very long time to repeat, and your eye never catches the loop.

.eq__bar {
  transform-origin: bottom;
  animation: eq-bounce var(--eq-speed) ease-in-out infinite alternate;
}
/* each bar: its own tempo, and a head start into its cycle */
.eq__bar:nth-child(1) { animation-duration: calc(var(--eq-speed) * .83);  animation-delay: calc(var(--eq-speed) * -.41); }
.eq__bar:nth-child(2) { animation-duration: calc(var(--eq-speed) * 1.17); animation-delay: calc(var(--eq-speed) * -.86); }
/* ...and so on for all eight */

@media (prefers-reduced-motion: reduce) {
  .eq__bar { animation: none; }   /* a calm, static meter */
}

A sketch of the technique as used in the playground above. The repo's stylesheet is src/eq.css on GitHub.

Animating transform rather than height matters too: the browser can move the bars on the graphics card without recalculating the page's layout on every frame, so a page full of equalizers stays smooth.

Quick start

Option A, one line: the optional 1 KB script builds the bars for you.

<link rel="stylesheet" href="src/eq.css">
<script src="src/eq.js" defer></script>

<span data-eq></span>

Option B, zero JavaScript: write the eight bars by hand.

<link rel="stylesheet" href="src/eq.css">

<span class="eq" role="img" aria-label="equalizer">
  <span class="eq__bar"></span> <!-- × 8 -->
</span>

Knobs

Set them per instance with data attributes (Option A) or CSS custom properties (either option):

KnobDefaultAttribute / CSS variable
Color#3fb950data-eq-color / --eq-color
Height28pxdata-eq-height / --eq-height
Bar width5pxdata-eq-width / --eq-width
Tempo1.1sdata-eq-speed / --eq-speed

From the repo's README. The playground starts taller than the 28px default so it's easier to see.

The hover reveal

On The DJ Calendar's homepage, the equalizer waits until you earn it. Each artist card holds it invisible, and on hover the whole reveal fades in together: a 75% black overlay with the "You are now listening to" label and track, plus the equalizer as a full-width strip along the bottom of the photo. On a 280px card, eight bars with 4px gaps span the full width at 80px tall. The card in the demo above uses that same geometry.

.card-image { position: relative; overflow: hidden; }
.card-overlay, .waveform-canvas { opacity: 0; transition: opacity 0.3s ease; pointer-events: none; }
.card-overlay { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.75); }
.waveform-canvas { position: absolute; bottom: 0; left: 0; width: 100%; height: 80px; }

.card-image:hover .card-overlay,
.card-image:hover .waveform-canvas { opacity: 1; }

Condensed from the repo's README. The repo also reveals on :focus, so keyboard visitors get the same experience as mouse users.

Accessible by default

The bars are decoration, so the injector marks them aria-hidden; if you write them by hand, wrap them in role="img" with an aria-label. And because the motion is plain CSS, one media query handles the most important courtesy: when a visitor's device asks for reduced motion, the bars stop and settle into a still meter. No JavaScript needed to be polite. Tick the box in the playground to see exactly what those visitors get.

When the bars follow real music

On the live site, the bars on the homepage cards are drawn on a canvas and driven by the actual audio: a 30-second iTunes preview plays through a Web Audio analyser, and each bar reads its own frequency band, so the bass bars on the left dance hardest. That full version, with the preview lookup, caching and race guards, is its own project, dj-card-preview.

One quirk it designs around: browsers block sound until a visitor has interacted with the page, so the first click unlocks audio and every hover after that plays. That rule was discovered live in production, and the experience was rebuilt around it.

Frequently asked questions

Does eq-visualizer need JavaScript to render?

No. You can write the eight bar elements by hand and use the supplied CSS. The optional small JavaScript file can inject the markup for you.

Is the CSS equalizer driven by live audio?

No. This widget is decorative CSS motion. The separate dj-card-preview project contains the canvas equalizer driven by real Web Audio frequencies.

How does it respect reduced-motion settings?

A prefers-reduced-motion media query stops the bar animations and leaves a calm static meter.

What can I customize?

You can customize the equalizer color, height, bar width, and tempo with data attributes or CSS custom properties.

Small details, done right

Bryan Hamilton is a full-stack web engineer with 25+ years in PHP and JavaScript, available immediately for remote, hybrid or on-site roles in the NYC metro area.