LOG 03 · TECHNIQUE

CSS3D + WebGL Hybrid Rendering

Real, accessible DOM content placed inside a genuine 3D WebGL scene using Three.js's CSS3DRenderer — text stays selectable and crawlable even while it orbits in 3D space.

Why not just render text in WebGL?

Baking UI into a WebGL canvas (as a texture, or via a text-mesh library) makes it fast to place in 3D space, but it stops being real content — no text selection, no in-page search, nothing for a screen reader or a search crawler to read. Three.js's CSS3DRenderer solves this the other way around: real DOM elements get positioned and rotated in true 3D, transformed via CSS matrix3d, while a second, ordinary WebGL renderer draws the scene behind them — both driven by the same camera, so perspective matches exactly between the two layers.

On this site, each section of the homepage is a real React component, portal-mounted into a div that CSS3DRenderer places on a helix path around the globe. Scroll or swipe drives a GSAP tween of the shared camera; CSS3DRenderer and the WebGL renderer both redraw off the same camera state every frame, so the DOM content and the 3D globe stay in perfect sync as if they occupied one scene.

The stacking-context gotcha

The one sharp edge of this technique: `perspective`, `transform`, and `filter` on any ancestor element create a new CSS stacking context — and become the containing block for `position:fixed` descendants. A fixed-position WebGL canvas that's supposed to sit behind everything can silently end up trapped inside the wrong stacking context the moment an ancestor picks up one of those properties, breaking layering in a way that's easy to misdiagnose as a z-index bug when it's actually a containing-block bug.

The fix is architectural, not a z-index patch: keep the WebGL canvas and the CSS3D container as true siblings at the document root, each with its own explicit z-index, rather than nesting either one inside an element that might someday gain a `transform` or `filter` for an unrelated reason.