Create an Audio Progress Bar with Howler.js
This article provides a step-by-step guide on how to implement a dynamic progress bar for audio tracks using the Howler.js library. You will learn how to set up the HTML interface, style the progress bar with CSS, and write the JavaScript logic to sync the progress bar with the audio playback, including how to enable user seeking by clicking on the progress bar.
1. The HTML Structure
To build a progress bar, you need a container element to represent the total duration of the track, and an inner element that expands to represent the current playback position.
<div id="controls">
<button id="play-pause-btn">Play</button>
</div>
<div id="progress-container">
<div id="progress-bar"></div>
</div>2. The CSS Styling
The container needs a fixed width and background color, while the
inner progress bar starts with a width of 0%. Setting the
cursor to pointer on the container indicates to users that it is
interactive.
#progress-container {
width: 100%;
height: 12px;
background-color: #e0e0e0;
border-radius: 6px;
cursor: pointer;
margin-top: 20px;
position: relative;
}
#progress-bar {
width: 0%;
height: 100%;
background-color: #3498db;
border-radius: 6px;
transition: width 0.1s linear;
}3. The JavaScript Implementation
To make the progress bar functional, you must initialize Howler.js,
update the progress bar width during playback using
requestAnimationFrame for smooth updates, and handle click
events to allow seeking.
// Initialize the Howl instance
const sound = new Howl({
src: ['your-audio-file.mp3'],
html5: true, // Enable HTML5 Audio to handle large files
onplay: function() {
// Start updating the progress bar when playback begins
requestAnimationFrame(updateProgress);
}
});
const playPauseBtn = document.getElementById('play-pause-btn');
const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar');
// Play/Pause toggle logic
playPauseBtn.addEventListener('click', () => {
if (sound.playing()) {
sound.pause();
playPauseBtn.textContent = 'Play';
} else {
sound.play();
playPauseBtn.textContent = 'Pause';
}
});
// Function to update the progress bar width
function updateProgress() {
const seek = sound.seek() || 0;
const duration = sound.duration() || 0;
if (duration > 0) {
const progressPercentage = (seek / duration) * 100;
progressBar.style.width = `${progressPercentage}%`;
}
// Continue updating if the track is still playing
if (sound.playing()) {
requestAnimationFrame(updateProgress);
}
}
// Enable seeking when clicking on the progress bar
progressContainer.addEventListener('click', (e) => {
const containerWidth = progressContainer.clientWidth;
const clickX = e.offsetX;
const duration = sound.duration();
if (duration > 0) {
// Calculate the new playback position in seconds
const newPosition = (clickX / containerWidth) * duration;
// Seek to the new position
sound.seek(newPosition);
// Immediately update the visual progress bar
progressBar.style.width = `${(clickX / containerWidth) * 100}%`;
}
});How the Code Works
sound.seek(): Retrieves the current playback position in seconds.sound.duration(): Retrieves the total duration of the audio file in seconds.requestAnimationFrame(): Synchronizes the visual updates with the browser’s refresh rate, ensuring the progress bar fills smoothly without performance lag.- Seek Logic: Clicking the progress bar calculates
the percentage of the click relative to the container’s total width.
Multiplying this percentage by the total duration of the track yields
the correct timestamp in seconds, which is then passed to
sound.seek().