How to Use Howler.js for Browser Game Audio

Howler.js is one of the most widely used audio libraries for indie browser games. It wraps the Web Audio API and HTML5 Audio into a single consistent interface, handles autoplay restrictions across browsers, supports audio sprites for tight latency, and is small enough to drop into any project. This tutorial walks through how to use Howler.js for browser games: loading sounds, playing them, mixing volume, building sprite sheets for SFX, and handling the autoplay rules every browser now enforces.
Key takeaways
- Howler.js is an open-source, MIT-licensed audio library that is a popular default for HTML5 games.
- It wraps Web Audio API + HTML5 Audio so you don’t write fallback code.
- Audio sprites pack many short SFX into one file for low-latency playback.
- Browsers block audio until the user interacts with the page; Howler unlocks audio on the first tap or click, but your game should still start sound from a user action.
- Use Howler.volume() for master volume; individual Howl instances for per-sound control.
What Howler.js solves
Browser audio is more complicated than it should be. The Web Audio API gives you precise scheduling but is verbose. HTML5 Audio is simple but has higher latency and worse browser support for some features. Mobile browsers and recent desktop browsers enforce autoplay rules that vary by vendor. Audio sprites — multiple sounds in one file — require manual offset tracking. Howler.js wraps all of this into a Howl object with .play(), .pause(), .stop(), .volume(), and a sprite definition you pass at construction.
The project describes the core as about 7KB gzipped. No dependencies. MIT licensed.
Installation
For a CDN drop-in (fine for prototypes), add to your HTML:
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.4/howler.min.js"></script>
For npm-based projects:
npm install howler
Then import it:
import { Howl, Howler } from 'howler';
The library exposes two main things: the Howl class (one Howl per sound or sound sprite) and the Howler global (master volume, codec detection, stop-all).
Loading and playing a sound
The minimal use case is three lines:
const jump = new Howl({
src: ['jump.mp3', 'jump.ogg']
});
jump.play();
Howler picks the first format in the list that the browser supports. The Howler documentation suggests WebM (Opus) first with an MP3 fallback for broad coverage; OGG, AAC, and other formats are also supported if you need them.
The Howl is created immediately but the audio file loads asynchronously. To know when it’s ready, listen for the ‘load’ event:
const jump = new Howl({
src: ['jump.mp3'],
onload: () => console.log('jump sound ready'),
onloaderror: (id, err) => console.error('failed to load:', err)
});
Audio sprites
For games with many short SFX, audio sprites reduce HTTP requests and tighten latency. Concatenate your SFX into a single file (e.g., sfx.mp3), then tell Howler the offset and duration of each named sound:
const sfx = new Howl({
src: ['sfx.mp3'],
sprite: {
jump: [0, 200],
coin: [300, 150],
explode: [500, 600],
pickup: [1200, 250]
}
});
sfx.play('jump');
sfx.play('coin');
The sprite values are [offset_ms, duration_ms]. Some workflows add a third value for looping: [offset, duration, true].
Tools for building sprite sheets: “audiosprite”, a third-party open-source command-line tool, takes a folder of sound files and outputs the combined sprite file plus a JSON definition that can be adapted for Howler. For most projects, that’s the right pipeline.
Master volume and per-sound volume
Master volume applies to all Howl instances:
Howler.volume(0.5); // 50% master
Per-sound volume on a Howl:
const music = new Howl({ src: ['music.mp3'], volume: 0.3 });
music.play();
// later, fade
music.fade(0.3, 1.0, 2000); // fade from 0.3 to 1.0 over 2s
For dynamic mixing (lower music when a UI sound plays, restore after), call .volume() with a new value on the music Howl.
Looping background music
const bgm = new Howl({
src: ['bgm.mp3'],
loop: true,
volume: 0.4,
html5: true
});
bgm.play();
The html5: true flag tells Howler to stream the file via HTML5 Audio instead of decoding it into Web Audio. For long files (anything over 30 seconds), stream them. Otherwise you waste memory and slow down initial load.
For short loops (under 10 seconds), default Web Audio playback is fine and lower latency.
The autoplay problem
Modern browsers prevent audio from playing before a user interaction. If you try to .play() a Howl on page load, the browser may block it and the audio stays silent. Howler’s autoUnlock feature (on by default) unlocks audio on the first touch, click, or key press, and you can listen for the ‘playerror’ and ‘unlock’ events to retry playback once audio is available.
The fix: gate the first play behind a user gesture. The standard pattern:
document.addEventListener('click', () => {
bgm.play();
}, { once: true });
Or, for games, on the game’s “Start” button:
startButton.addEventListener('click', () => {
bgm.play();
startGame();
});
After the first user gesture, subsequent Howler audio can normally play freely for the rest of that page session.
Stopping and pausing
jump.play(); // returns sound ID
jump.pause(); // pauses all
jump.stop(); // stops all
// for specific instance
const id = jump.play();
jump.stop(id); // stops just this one
For a game with many overlapping SFX, the per-ID stop is critical. You can fire 50 coin sounds and stop only the most recent one.
Spatial audio (panning)
Howler supports stereo panning and 3D spatial audio for game-relevant positioning. These methods come from the spatial plugin, which is included in the full howler.js build but not in howler.core:
const enemy = new Howl({ src: ['growl.mp3'] });
const id = enemy.play();
enemy.stereo(-0.7, id); // pan left
// or 3D
enemy.pos(10, 0, 0, id); // position the sound in 3D
Howler.pos(0, 0, 0); // listener position
For 2D games, stereo panning by the sound source’s relative x-position is usually enough. For top-down games where the camera moves, recompute the pan on each frame:
const dx = enemy.x - player.x;
const pan = Math.max(-1, Math.min(1, dx / 400));
growlHowl.stereo(pan, growlId);
Common mistakes
Three mistakes new Howler users hit constantly:
- Forgetting the user-gesture rule: Calling .play() in window.onload can be blocked on autoplay-restricted browsers. Gate the first play behind a click or key press.
- Decoding huge files into Web Audio: Web Audio stores decoded, uncompressed samples, so a 5-minute stereo track at 44.1 kHz takes roughly 100MB as 32-bit float data, far more than the compressed file. Set html5: true for long audio.
- Not preloading critical SFX: A Howl that hasn’t finished loading when you call .play() will queue the play but not respond immediately. For latency-sensitive sounds (jump, shoot), wait for the ‘load’ event or use audio sprites that pre-decode at startup.
Performance notes
Howler is fast enough for the audio of most browser games. Performance-tuning notes:
- Pre-allocate Howls. Don’t create a new Howl every time you play a sound. Reuse one Howl per asset.
- Audio sprites for SFX. One file, one decode, all sounds available with minimal latency.
- html5: true for music. Streams the file, saves memory.
- Limit concurrent sounds. Each playing sound has a small overhead, and very large numbers of overlapping sounds can cause glitches, so cap how many copies of the same SFX can play at once.
Howler vs alternatives
The main alternatives:
- Web Audio API directly: More control, more code, no autoplay handling, no sprite support out of the box. Use this if you need precise scheduling for music or for procedural audio.
- Phaser’s built-in audio: If you’re using Phaser, the engine has its own audio system that handles most of what Howler does. Use Phaser’s audio for Phaser games unless you need a specific Howler feature.
- Tone.js: For music-game-specific work or synth-heavy projects. Different scope entirely.
For most browser games, Howler is the right default. For broader context on getting audio into a browser game, see our piece on adding sound to a browser game.
Frequently asked questions
Is Howler.js free?
Yes. MIT licensed and open source. Use it commercially without restriction.
Does Howler.js work on mobile browsers?
Yes. iOS Safari and Chrome on Android both work. The autoplay rules are stricter on mobile, so the user-gesture pattern is mandatory there.
What audio formats should I use?
MP3 is supported by every major modern browser, so it makes a safe fallback. Listing WebM (Opus) first and MP3 second, as the Howler docs suggest, gives smaller files where supported. Avoid shipping a single format other than MP3, because support for formats like OGG has historically varied, especially on Safari.
How do I handle the autoplay restriction?
Gate your first audio play behind a user gesture (click, key press, tap). Howler unlocks audio automatically on that first interaction, after which subsequent Howler audio can normally play. The standard pattern is a “Start” button or a one-time document click listener.
Can Howler handle 3D positional audio for first-person games?
Yes. Howler’s spatial plugin (bundled in the full build) supports Web Audio’s 3D panning via .pos() on the Howl and Howler.pos() on the global listener. For first-person browser games, this is usable but you may want to use the Web Audio API directly for finer control.
The takeaway
How to use Howler.js for browser games comes down to: create a Howl per asset (or per sprite sheet), gate the first .play() behind a user gesture, use html5: true for long music, and audio sprites for short SFX. Howler abstracts the messy parts of browser audio while staying lightweight. For a quick break between coding sessions, the Chrome Dino game plays fine with or without its sound effects.








