Tutorials 5 min read

How to create a music visualizer with Tailwind CSS and JavaScript

Build a music visualizer with Tailwind CSS and JavaScript using the Web Audio API and canvas: bars that react to an audio file in real time.

Today we’re going to grab our dancing shoes, because we’re going to create a music visualizer using Tailwind CSS, the Web Audio API and a canvas element.

What is a music visualizer?

A music visualizer takes an audio signal, analyzes it in real time, and translates it into animations that respond to the music: beats, rhythm, frequency and amplitude. You’ve seen them in media players like Winamp or VLC, on YouTube channels that publish ambient or electronic music, in DAWs like Ableton Live as visual feedback, and on stage behind DJs. Ours will be the classic version: a row of colored frequency bars dancing above the track.

Use cases

  • Audio players: give a podcast or music player some visual life while the track plays.
  • Streaming content: generate moving visuals for audio-only videos instead of a static cover.
  • Landing pages: a reactive canvas makes an audio product demo far more engaging.
  • Live dashboards: the same analyser technique works for meters and level indicators.

Now, the markup

The audio element

The audio element plays the audio file. We are using the native HTML audio element, but a library like Howler.js would work the same way.

Ids

  • audio: assigns a unique ID to the audio element so we can access it in JavaScript.

Attributes

  • controls: adds the native play/pause controls.
  • src: the path to the audio file.

The canvas element

The canvas element is where the visualizer is drawn.

Ids

  • visualizer: assigns a unique ID to the canvas element so we can access it in JavaScript.
html
<div class="items-center w-full max-w-xl mx-auto">
  <audio id="audio" controls src="/music/sample.mp3"></audio>
  <canvas id="visualizer"></canvas>
</div>

The JavaScript code

The declarations

We grab the audio element, the canvas, and its 2D drawing context.

  • const audio = document.getElementById("audio");: the audio element.
  • const canvas = document.getElementById("visualizer");: the canvas element.
  • const ctx = canvas.getContext("2d");: the context we draw the bars with.
js
const audio = document.getElementById("audio");
const canvas = document.getElementById("visualizer");
const ctx = canvas.getContext("2d");

The state

These start empty and get filled in the moment the user hits play.

js
let audioContext;
let analyser;
let dataArray;

The initialization function

initializeAudio runs on the first play event. It creates the audio context, the analyser node, and the data array the analyser writes into.

  • if (!audioContext): only initialize once; later plays reuse the same context.
  • audioContext = new (window.AudioContext || window.webkitAudioContext)();: creates the audio context.
  • analyser = audioContext.createAnalyser();: creates the analyser node.
  • analyser.fftSize = 256;: sets the FFT size; half of that (128) becomes the number of frequency bins, which is the number of bars.
  • dataArray = new Uint8Array(bufferLength);: the array the frequency data is copied into on every frame.
  • const source = audioContext.createMediaElementSource(audio);: wraps the audio element as a source node.
  • source.connect(analyser); and analyser.connect(audioContext.destination);: audio flows through the analyser and out of the speakers.
  • requestAnimationFrame(visualize);: starts the drawing loop.
js
function initializeAudio() {
  if (!audioContext) {
    audioContext = new (window.AudioContext || window.webkitAudioContext)();
    analyser = audioContext.createAnalyser();
    analyser.fftSize = 256;
    const bufferLength = analyser.frequencyBinCount;
    dataArray = new Uint8Array(bufferLength);
    const source = audioContext.createMediaElementSource(audio);
    source.connect(analyser);
    analyser.connect(audioContext.destination);
  }
  requestAnimationFrame(visualize);
}
audio.addEventListener("play", initializeAudio);

The visualization function

visualize runs every frame: it reads the current frequency data, clears the canvas, and draws one bar per frequency bin.

  • const barWidth = (WIDTH / dataArray.length) * 2.5;: bar width derived from the canvas width and the number of bins.
  • analyser.getByteFrequencyData(dataArray);: copies the current frequency values (0 to 255) into the array.
  • ctx.fillRect(0, 0, WIDTH, HEIGHT);: repaints the dark background each frame.
  • const barHeight = dataArray[i] / 2;: louder frequency, taller bar.
  • const hue = (i / dataArray.length) * 360;: walks the color wheel from left to right.
  • requestAnimationFrame(visualize);: keeps the loop going.
js
function visualize() {
  const WIDTH = canvas.width;
  const HEIGHT = canvas.height;
  const barWidth = (WIDTH / dataArray.length) * 2.5;
  analyser.getByteFrequencyData(dataArray);
  ctx.fillStyle = "rgb(17, 24, 39)";
  ctx.fillRect(0, 0, WIDTH, HEIGHT);
  for (let i = 0; i < dataArray.length; i++) {
    const barHeight = dataArray[i] / 2;
    const hue = (i / dataArray.length) * 360;
    ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
    ctx.fillRect(i * barWidth, HEIGHT - barHeight, barWidth, barHeight);
  }
  requestAnimationFrame(visualize);
}

The full JavaScript code

js
const audio = document.getElementById("audio");
const canvas = document.getElementById("visualizer");
const ctx = canvas.getContext("2d");
let audioContext;
let analyser;
let dataArray;

function initializeAudio() {
  if (!audioContext) {
    audioContext = new (window.AudioContext || window.webkitAudioContext)();
    analyser = audioContext.createAnalyser();
    analyser.fftSize = 256;
    const bufferLength = analyser.frequencyBinCount;
    dataArray = new Uint8Array(bufferLength);
    const source = audioContext.createMediaElementSource(audio);
    source.connect(analyser);
    analyser.connect(audioContext.destination);
  }
  requestAnimationFrame(visualize);
}
audio.addEventListener("play", initializeAudio);

function visualize() {
  const WIDTH = canvas.width;
  const HEIGHT = canvas.height;
  const barWidth = (WIDTH / dataArray.length) * 2.5;
  analyser.getByteFrequencyData(dataArray);
  ctx.fillStyle = "rgb(17, 24, 39)";
  ctx.fillRect(0, 0, WIDTH, HEIGHT);
  for (let i = 0; i < dataArray.length; i++) {
    const barHeight = dataArray[i] / 2;
    const hue = (i / dataArray.length) * 360;
    ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
    ctx.fillRect(i * barWidth, HEIGHT - barHeight, barWidth, barHeight);
  }
  requestAnimationFrame(visualize);
}

Conclusion

That’s a working music visualizer: the audio element feeds an analyser node, and a canvas loop turns the frequency data into colored bars. From here you can play with fftSize for more or fewer bars, swap the hsl formula for your brand colors, or draw waveforms instead of bars.

I hope you found this tutorial helpful and have a great day!

/Michael Andreuzza