The three ways to add textures to HTML games
Textures in HTML games come from image files you load into your code and map onto 3D shapes or 2D sprites. The method you use depends on whether you're building with Canvas (2D drawing), WebGL (raw 3D), or a game framework like Babylon.js or Three.js. Each approach loads images differently and positions them on your game objects in different ways.
For Canvas games, you load an image file and draw it directly onto your canvas. For WebGL and frameworks, you load the image as a texture — a special GPU resource that wraps around 3D geometry. The difference matters because WebGL textures are optimized for 3D rendering, while Canvas images are just pixels you're drawing in 2D space.
Key Takeaways
- Canvas games load image files with the Image object and draw them using drawImage(), which works for sprites and 2D backgrounds.
- WebGL and 3D frameworks require you to create a texture object from an image file and bind it to your geometry using UV coordinates.
- Image files must be hosted on the same domain as your game or served with CORS headers to avoid security errors.
- Texture size matters for performance — powers of 2 (256×256, 512×512, 1024×1024) load faster on older devices than arbitrary sizes.
- You can load multiple textures at once and switch between them, or use texture atlases to pack many small images into one file.
Loading images for Canvas 2D games
In Canvas games, you create a new Image object, set its src to your image file path, and wait for it to load before drawing it. The image must be fully loaded before you try to draw it, or you'll get a blank result. Use the onload event to know when it's ready.
Here's the pattern: create the Image, set onload to a function that marks it as ready, set the src to trigger the load, then in your game loop check if it's ready before drawing. If you try to draw before onload fires, nothing appears.
Once loaded, use ctx.drawImage(image, x, y) to draw it at a position, or ctx.drawImage(image, x, y, width, height) to scale it. You can also rotate or flip it by using ctx.save(), ctx.translate(), ctx.rotate(), then ctx.drawImage(), then ctx.restore().
Setting up textures for WebGL and 3D frameworks
WebGL textures are GPU objects that wrap around your 3D geometry. You load an image file, create a WebGL texture from it, and bind it to your mesh using UV coordinates — numbers from 0 to 1 that tell WebGL which part of the image goes on which part of your shape.
If you're using Three.js, the TextureLoader handles this for you: create a new THREE.TextureLoader(), call load() with your image path, and pass the result to your material's map property. Three.js handles UV coordinates automatically for standard geometries like boxes and spheres.
If you're using Babylon.js, create a new BABYLON.Texture() with your image path and assign it to your material's diffuse texture property. Both frameworks handle the GPU setup behind the scenes — you just pass the image path and the framework creates the texture object.
Raw WebGL texture creation and binding
If you're writing WebGL directly without a framework, you create a texture with gl.createTexture(), bind it with gl.bindTexture(), load the image data with gl.texImage2D(), and set parameters like filtering and wrapping. This is more code but gives you full control.
The steps are: create the texture, bind it, set texture parameters (how it filters when zoomed in or out, how it wraps at edges), load the image data, then unbind. You'll also need to set UV coordinates in your vertex shader so WebGL knows which part of the texture to use on each vertex of your geometry.
Most developers use a framework instead because raw WebGL texture setup is verbose and error-prone. But if you need custom filtering or unusual texture formats, raw WebGL gives you that control.
Handling image loading errors and CORS issues
Images must load from the same domain as your game, or the server hosting the image must send CORS (Cross-Origin Resource Sharing) headers. If you load an image from a different domain without CORS headers, the browser blocks it as a security measure.
If you're testing locally with file:// URLs, some browsers block image loading entirely. Use a local web server instead — Python's http.server module or Node's http-server package will serve your files with the right headers. If you're deploying to a real server, make sure your image files are in a folder the server can reach.
In Canvas, if an image fails to load, drawImage() silently does nothing. In WebGL, a failed texture load usually shows as white or black. Add error handlers to your Image objects or texture loaders so you know when something went wrong: in Canvas, use img.onerror = function() { console.log('image failed'); }, and in Three.js, pass an error callback to TextureLoader.load().
Texture size, performance, and atlasing
Texture sizes that are powers of 2 (256×256, 512×512, 1024×1024, 2048×2048) load and render faster than arbitrary sizes like 800×600. This is a GPU hardware thing — older graphics cards have built-in optimizations for power-of-2 textures. Modern devices are more forgiving, but power-of-2 is still the safe choice.
If you have many small textures, combine them into a single image file called a texture atlas and use UV coordinates to pick out the part you need. Instead of loading 20 separate images, you load one large image and tell WebGL which rectangle within it belongs to each object. This reduces the number of texture switches your GPU has to make, which speeds up rendering.
For Canvas games, you can do the same thing with drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight) — the middle four parameters let you crop a region from your atlas image and draw only that part.
Animating textures and swapping between them
To animate a texture in Canvas, use a texture atlas with frames arranged in a grid, and change which frame you're drawing each game loop. Keep a frame counter, increment it each loop, and use modulo to loop back to frame 0 when you reach the end. Then calculate which rectangle in your atlas corresponds to that frame and draw it.
In WebGL frameworks, you can swap textures by changing the material's texture property. Create multiple textures from different images, store them in an array, and switch between them based on game state. Three.js and Babylon.js both let you reassign textures without recreating the mesh.
For smooth animation, keep your frame rate consistent — if your game loop runs at 60 FPS but you want animation at 10 FPS, increment your frame counter only every 6 game loops. This keeps animation speed independent of frame rate.
Frequently Asked Questions
Can I use PNG, JPG, and WebP images as textures?
Yes, all three work in Canvas and WebGL. PNG supports transparency, which is useful for sprites. JPG is smaller but doesn't support transparency. WebP is newer and smaller than both, but older browsers don't support it. For game textures, PNG is the safest choice unless file size is critical.
What happens if my texture image is not a power of 2?
In Canvas, nothing special — it draws fine. In WebGL, non-power-of-2 textures work on modern devices but may not support wrapping or mipmapping, and performance can be slower. If you're targeting older devices or need those features, resize your image to a power of 2 using an image editor before loading it.
How do I flip or rotate a texture?
In Canvas, use ctx.scale(-1, 1) before drawing to flip horizontally, or ctx.rotate() to rotate. In WebGL frameworks, you can modify UV coordinates in your shader or use material properties — Three.js materials have flipY and rotation properties. Raw WebGL requires shader changes.
Can I load textures from a URL on a different website?
Only if that website sends CORS headers allowing it. If it doesn't, the browser blocks the load for security reasons. You can't force a website to send CORS headers — you'd need to download the image and host it on your own domain instead.
What's the difference between a texture and a sprite?
A sprite is a 2D image drawn at a position on screen — used in Canvas games. A texture is an image wrapped around 3D geometry — used in WebGL. In practice, a sprite is just a texture drawn in 2D space, so the concepts overlap. The difference is mainly about how the rendering engine treats them.