I run The DJ Calendar, a real-time EDM tour date platform. The homepage has 99 artist cards. I wanted you to be able to hover any of them and hear their music instantly — without navigating away, without a page load, without a framework.
Here's how I built it.
The Stack
Zero. No React, no Vue, no jQuery, no audio player library. Just vanilla JavaScript and two browser APIs that ship with every modern browser:
- iTunes Search API — free 30-second audio previews
- Web Audio API — real frequency analysis for the equalizer
Step 1: Getting the Audio
Apple's iTunes Search API is a public endpoint that doesn't need authentication. A single request with entity=musicTrack&limit=12&media=music returns preview URLs for any artist.
fetch(`https://itunes.apple.com/search?term=${encodeURIComponent(name)}&entity=musicTrack&limit=12&media=music`)
.then(r => r.json())
.then(data => {
const result = data.results.find(r => r.previewUrl);
if (result) {
card.dataset.previewUrl = result.previewUrl;
card.dataset.previewTitle = result.trackName;
} else {
card.dataset.previewMiss = '';
}
});
The preview URL gets cached on the card's data- attribute. First hover does the lookup. Every hover after that is instant.
Step 2: The Analyser — Real Frequencies, Not a Fake
This is the part I'm proudest of. The 8-bar equalizer isn't animated CSS pretending to be sound. It reads actual frequency data from the audio.
const audio = new Audio();
audio.crossOrigin = 'anonymous';
const ctx = new AudioContext();
const src = ctx.createMediaElementSource(audio);
const analyser = ctx.createAnalyser();
analyser.fftSize = 64;
analyser.smoothingTimeConstant = 0.8;
src.connect(analyser);
analyser.connect(ctx.destination);
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
function draw() {
analyser.getByteFrequencyData(dataArray);
// Clear canvas, draw 8 bars from dataArray[i*2]
requestAnimationFrame(draw);
}
The fftSize of 64 gives us 32 frequency bins. Reading every other one (dataArray[i*2]) gives us exactly 8 bars. Each bar represents a real slice of the audio spectrum.
Step 3: The Race Condition Guard
Sweep your cursor across a row of cards fast and three things race: the audio load, the canvas draw loop, and your mouse leaving. Here's the fix:
card._djcPreviewGen = (card._djcPreviewGen || 0) + 1;
const gen = card._djcPreviewGen;
// Inside every async callback:
if (gen !== card._djcPreviewGen) return; // stale, abort
New hover bumps the counter. Every previous hover's callbacks check before touching the DOM. The orphaned callbacks just die silently.
Step 4: Consent-First Design
Most hover previews break on the devices where hover doesn't exist. This one treats every "no" as a first-class answer.
Touch devices (hover: none, pointer: coarse): hover never fires. A tap on the photo toggles play/stop. The tap also satisfies the autoplay policy — no extra click needed.
Reduced motion (prefers-reduced-motion: reduce): no hover preview at all. Motion-reactive feedback is exactly what these visitors asked the OS to skip.
No preview found: the card gets data-preview-miss and is silently skipped forever. No repeated failing lookups on every hover.
@media (hover: none) and (pointer: coarse) {
.dj-card .card-image-overlay { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.dj-card .card-image-overlay { display: none; }
}
Step 5: The Extras
A few things that came for free once the audio pipeline was in place:
- Document title swap: while a preview plays, the tab reads "David Guetta - Titanium (feat. Sia)". Restores on stop.
- Media Session API: the track shows up in OS media controls with artwork.
-
Cross-origin audio:
audio.crossOrigin = 'anonymous'is what makes the analyser legal on Apple's CDN. Without it, the analyser returns all zeros.
The Result
A hover preview that works on every device, respects every accessibility setting, handles race conditions, and drives a real audio-reactive equalizer — all in vanilla JS, zero dependencies.
[Try it Live] https://bryanhamiltondev.github.io/dj-card-preview/ — click the page once, then hover the card.
The full source is on GitHub: bryanhamiltondev/dj-card-preview
This is part of a series extracting production patterns from The DJ Calendar. Also check out the eq-visualizer (pure-CSS decorative EQ) and next-show-radar (consent-first tour map).
Top comments (0)