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.