Three.js From Zero · Article s7-02
S7-02 HUDs & Screen Overlays
Season 7 · Article 02
HUDs & Screen Overlays
Crosshair, HP bar, minimap, ammo counter, notification toast. All just HTML and CSS over your canvas. No three.js involved — except for the data feeding them.
1. The setup
<div style="position:relative;width:100%;aspect-ratio:16/9">
<canvas></canvas> <!-- z: auto (0) -->
<div class="hud">...</div> <!-- position:absolute, on top -->
</div>
2. Pointer-events policy
.hud-root { position: absolute; inset: 0; pointer-events: none; }
.hud-root > * { pointer-events: auto; }
Otherwise the HUD catches your 3D scene's mouse events. Transparent-by-default container with opt-in interactivity.
3. HUD elements in this demo
- Crosshair: center overlay, pure CSS.
- HP bar: bottom-left, gradient fill animated.
- Minimap: top-right, canvas 2D drawing.
- Notification toast: center, slide-in animation.
4. The minimap pattern
const map = new OffscreenCanvas(128, 128);
const ctx = map.getContext('2d');
function drawMinimap() {
ctx.clearRect(0, 0, 128, 128);
// player at center
ctx.fillStyle = '#22d3ee';
ctx.fillRect(62, 62, 4, 4);
// enemies in world space → map space
for (const enemy of enemies) {
const dx = enemy.pos.x - player.pos.x;
const dz = enemy.pos.z - player.pos.z;
const mx = 64 + dx * 0.3; // scale world to map
const mz = 64 + dz * 0.3;
ctx.fillStyle = '#ff4444';
ctx.fillRect(mx-2, mz-2, 4, 4);
}
}
5. React HUD
function HUD({ hp, maxHp, ammo, ammoMax, notification }) {
return (
<div className="hud-root">
<Crosshair />
<HealthBar hp={hp} max={maxHp} />
<Minimap />
<AmmoCount ammo={ammo} max={ammoMax} />
{notification && <Toast message={notification} />}
</div>
);
}
Drive state from a Zustand/Jotai store that your 3D loop writes to. No per-frame DOM rewrites.
6. Live demo — FPS-style HUD
–
7. When to avoid HTML
- Photo-mode screenshots: HTML doesn't burn into the render. Use canvas-texture for a "baked-in" UI.
- Post-processing: bloom/glow on HUD requires drawing UI INTO the 3D render.
- VR: HTML doesn't compose into HMD frame buffer.
8. Throttling high-rate values
// Don't update DOM at 60fps for a stats counter
let last = 0;
function updateStats() {
const now = performance.now();
if (now - last > 100) { // 10Hz max
hpElement.textContent = player.hp;
last = now;
}
}
9. Takeaways
- HUD is HTML + CSS over canvas. Nothing fancier.
- Container:
pointer-events: none. Children:auto. - Minimap via OffscreenCanvas + manual draws.
- React/Svelte/etc. naturally, drive from 3D loop's state.
- Throttle high-rate updates.