HTML5 game publishing
Sprite Atlases for HTML5 Games: Fewer Requests, Fewer Draw Calls
When texture atlases help, how to group and build them, size and memory limits, padding, trimming and formats.
A 2D game with hundreds of separate image files often loads slowly and can run worse than it should. Every file is a separate request, and in WebGL renderers, switching between many textures can increase draw calls. Sprite atlases, also called texture atlases or sprite sheets, pack many images into one. This post explains when atlases help, how to build them, and the mistakes to avoid.
What an atlas is
An atlas is one large image that contains many smaller images, plus a data file that says where each one is: its position, size and sometimes rotation or trimmed borders. The game loads one image and one data file instead of hundreds of files, and draws parts of the big image.
Why it helps
Fewer requests
Each file has overhead: a request, headers, and on portals sometimes a separate trip through a CDN. HTTP/2 and HTTP/3 make many small requests cheaper than they used to be, but hundreds of tiny files still add up, especially on slow mobile connections. A few atlases load faster and more predictably.
Fewer texture switches and draw calls
In WebGL-based renderers (Phaser in WebGL mode, PixiJS, Three.js for 2D sprites), sprites that share one texture can often be drawn together in a single batch. Sprites from different textures usually break the batch. Putting sprites that appear together into the same atlas lets the renderer batch them, which can reduce draw calls a lot.
With the plain Canvas 2D API, there is no batching in the same sense, so the performance gain is smaller. The loading benefit still applies.
How to build atlases
You rarely pack atlases by hand. Common options:
- Dedicated packing tools that export atlases in formats engines understand, often a JSON format (sometimes called "JSON hash" or "JSON array").
- Engine tools: many engines and frameworks have built-in or plugin-based packers.
- Command-line packers in your build step, so atlases are rebuilt whenever art changes.
Whatever you use, put atlas generation in your build process rather than doing it manually. A manually packed atlas falls out of date the first time an artist changes a sprite.
Grouping: what goes in which atlas
One giant atlas for everything is usually a mistake. Group sprites by when they are used:
- UI atlas: buttons, icons, fonts. Needed everywhere.
- Per-world or per-level atlases: the tiles and enemies of one world. Only load what the current level needs.
- Character atlas: the player's animations.
Sprites drawn together on screen should share an atlas, so they can be batched. Sprites never seen together can live in different atlases.
Size limits
Every GPU has a maximum texture size. Many devices support 4096×4096 pixels, and many modern ones support more, but you cannot assume it. You can check with gl.getParameter(gl.MAX_TEXTURE_SIZE). Keeping atlases at 2048×2048 or smaller is a common safe choice for games that must run on older phones.
Memory also matters. A decoded image uses about 4 bytes per pixel in GPU memory, no matter how small the PNG or WebP file was. A 2048×2048 atlas uses about 16 MB once decoded. Several large atlases can exhaust memory on low-end phones, which can crash the tab.
Padding and bleeding
When sprites are packed tightly, scaling and texture filtering can pick up pixels from neighboring sprites. You see thin lines or colored edges around sprites, especially when the camera zooms or moves at fractional positions. Fixes:
- Padding: leave 1 to 2 pixels of empty space between sprites.
- Extrusion: copy each sprite's edge pixels outward into the padding, so any bleed picks up the same color. This matters most for tiles that must join seamlessly.
- Pixel art: use nearest-neighbor filtering and draw at whole-pixel positions.
Trimming
Packers can trim transparent borders from sprites to save space, and store the original size so the engine can draw them in the right place. This saves a lot of space for sprites with big transparent areas. Check that your engine supports trimmed frames, or animations will jump around.
Formats and compression
- PNG: lossless, good for pixel art and sharp UI.
- WebP: supported by current major browsers, usually much smaller. Lossless WebP is a safe swap for PNG in most cases. Lossy WebP works well for painted art.
- GPU compressed textures (such as Basis Universal or KTX2 formats) reduce GPU memory, not just download size. They are more complex to set up and mainly worth it for large 3D or very art-heavy games.
File size and GPU memory are different budgets. A tiny WebP file can still become a large texture in memory.
Power-of-two sizes
WebGL 1 has restrictions on textures whose sizes are not powers of two (such as 1000×700): mipmaps and repeat wrapping are not available for them. WebGL 2 removes most of these limits. For atlases drawn as sprites without mipmaps, non-power-of-two sizes usually work fine, but if you target WebGL 1 or need mipmaps, choose power-of-two atlas sizes.
Measuring the result
- Count requests in the Network panel before and after.
- Check draw calls with your engine's debug tools or a WebGL inspector.
- Compare loading time on a throttled mobile network profile.
- Watch memory on a low-end phone.
If draw calls did not drop, check whether sprites drawn together really share an atlas, and whether something between them (for example a text object or a different blend mode) breaks the batch.
Checklist
- Atlases generated in the build step.
- Grouped by when sprites are used, UI separate.
- Atlas size within a safe limit for your target devices.
- GPU memory considered, not just file size.
- Padding and extrusion against bleeding.
- Trimming, if your engine supports it.
- WebP or PNG chosen per atlas.
- Draw calls and requests measured before and after.
For more on keeping a game small and fast for portals, see the developer blog.


