From d2305ac9a123d25e480f729d61268f25db0637a8 Mon Sep 17 00:00:00 2001 From: Johnathon Selstad Date: Fri, 23 Jan 2026 14:16:14 -0800 Subject: [PATCH 01/12] Add webgl_overlay example Demonstrates a technique for rendering three.js canvases that integrate with scrollable HTML content. 3D elements can appear both in front of and behind HTML elements using dual WebGL renderers with split near/far clipping planes. Based on ThreeOverlay by Johnathon Selstad. Co-Authored-By: Claude Opus 4.5 --- examples/files.json | 1 + examples/webgl_overlay.html | 492 ++++++++++++++++++++++++++++++++++++ 2 files changed, 493 insertions(+) create mode 100644 examples/webgl_overlay.html diff --git a/examples/files.json b/examples/files.json index a68d269d829064..c4e6b72362c606 100644 --- a/examples/files.json +++ b/examples/files.json @@ -182,6 +182,7 @@ "webgl_multiple_elements_text", "webgl_multiple_scenes_comparison", "webgl_multiple_views", + "webgl_overlay", "webgl_panorama_cube", "webgl_panorama_equirectangular", "webgl_points_billboards", diff --git a/examples/webgl_overlay.html b/examples/webgl_overlay.html new file mode 100644 index 00000000000000..ad6ea4c1683903 --- /dev/null +++ b/examples/webgl_overlay.html @@ -0,0 +1,492 @@ + + + + three.js webgl - overlay + + + + + + +
+
+ +
+

WebGL Overlay

+

+ This example demonstrates a technique for rendering three.js canvases + that integrate with scrollable HTML content. 3D elements can appear both + in front of and behind HTML elements. +

+ +

How It Works

+

+ The overlay system uses two separate WebGL renderers: one positioned behind + the HTML content (background) and one in front (foreground). By adjusting + the camera's near and far clipping planes for each render pass, objects + closer than a threshold depth render in the foreground, while objects + further away render in the background. +

+

+ The camera position is synchronized with the scroll position of the page, + creating the illusion that the 3D scene exists in the same coordinate + space as the HTML document. A forced pixels-per-meter calculation ensures + consistent sizing across different viewport dimensions. +

+ +

Element Mapping

+

+ HTML elements can be mapped to 3D representations. In this demo, paragraph + elements are visualized as wireframe boxes in 3D space, allowing for + potential interaction between 3D objects and page content. +

+ +

Keymappings

+ + + + + + + + + + + +
ActionKey
Toggle Rendering Mode1, 2, 3
Toggle PositioningP
+

+ Press 1 for background only, 2 for foreground only, + or 3 for combined foreground and background rendering. +

+ +

Applications

+

+ This technique enables creative web experiences where 3D graphics seamlessly + blend with traditional HTML layouts. Use cases include decorative page + elements, interactive visualizations embedded in articles, and immersive + scrolling experiences. +

+ +

Credits

+

+ Based on ThreeOverlay + by Johnathon Selstad. +

+ +
Scroll to see 3D elements interact with the page.
+
+ + + + + + From b291638ef20dd10520feabe8f8e49eed411f995e Mon Sep 17 00:00:00 2001 From: Johnathon Selstad Date: Fri, 23 Jan 2026 16:28:12 -0800 Subject: [PATCH 02/12] Fix overlay example for Safari and add screenshot - Use absolute positioning with transform on Safari browsers - Conditionally allow scrolling in iframe for overlay examples - Add screenshot/thumbnail for examples page Co-Authored-By: Claude Opus 4.5 --- examples/index.html | 13 +++-- examples/screenshots/webgl_overlay.jpg | Bin 0 -> 36773 bytes examples/webgl_overlay.html | 69 ++++++++++++++++--------- 3 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 examples/screenshots/webgl_overlay.jpg diff --git a/examples/index.html b/examples/index.html index ca3bbf887b3d29..bcdef041904e2b 100644 --- a/examples/index.html +++ b/examples/index.html @@ -187,13 +187,20 @@

three.js

}; - // iOS iframe auto-resize workaround + // Safari iframe auto-resize workaround - if ( /(iPad|iPhone|iPod)/g.test( navigator.userAgent ) ) { + if ( /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) ) { viewer.style.width = getComputedStyle( viewer ).width; viewer.style.height = getComputedStyle( viewer ).height; - viewer.setAttribute( 'scrolling', 'no' ); + + // Allow scrolling for overlay examples + const currentExample = window.location.hash.substring( 1 ); + if ( ! currentExample.includes( 'overlay' ) ) { + + viewer.setAttribute( 'scrolling', 'no' ); + + } } diff --git a/examples/screenshots/webgl_overlay.jpg b/examples/screenshots/webgl_overlay.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b515e18c51501b062d2c8005e8edb35a2ad30a6c GIT binary patch literal 36773 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 literal 0 HcmV?d00001 diff --git a/examples/webgl_overlay.html b/examples/webgl_overlay.html index ad6ea4c1683903..cb83f7ffb69500 100644 --- a/examples/webgl_overlay.html +++ b/examples/webgl_overlay.html @@ -14,14 +14,18 @@ } #canvas-bg, #canvas-fg { - position: absolute; - width: 100%; - height: 100%; + position: fixed; + width: 100vw; + height: 100vh; top: 0; left: 0; pointer-events: none; } + #canvas-bg canvas, #canvas-fg canvas { + pointer-events: none; + } + #canvas-bg { z-index: -100; } @@ -139,7 +143,6 @@

Keymappings

Toggle Rendering Mode1, 2, 3 - Toggle PositioningP

@@ -182,10 +185,11 @@

Credits

let pixelsPerMeter, forcedPixelsPerMeter = 100.0; let cameraDepth = 5.0; let renderingMode = 2; // 0: bg only, 1: fg only, 2: both - let positioningMode = 1; // 0: fixed, 1: absolute + let isMobile = false; const elementBoxes = []; let cube; + let contentElement; init(); @@ -193,6 +197,9 @@

Credits

containerBg = document.getElementById( 'canvas-bg' ); containerFg = document.getElementById( 'canvas-fg' ); + contentElement = document.querySelector( '.content' ); + + checkMobile(); // Scene scene = new THREE.Scene(); @@ -241,13 +248,13 @@

Credits

} - // Demo cube (positioned in 3D space relative to page) + // Demo cube (positioned in 3D space relative to content) cube = new THREE.Mesh( new THREE.BoxGeometry( 1, 1, 1 ), new THREE.MeshPhysicalMaterial( { color: 0x00ff00 } ) ); - cube.position.set( ( window.innerWidth * 0.5 ) / pixelsPerMeter, -5.0, 0.0 ); scene.add( cube ); + updateCubePosition(); // Background renderer rendererBg = new THREE.WebGLRenderer( { antialias: true } ); @@ -310,6 +317,15 @@

Credits

} + function updateCubePosition() { + + const rect = contentElement.getBoundingClientRect(); + const centerX = rect.left + window.scrollX + rect.width * 0.5; + const cubeY = -5.0; // Fixed vertical position in 3D space + cube.position.set( centerX / pixelsPerMeter, cubeY, 0.0 ); + + } + function recomputeElementBoxes() { const boxGeometry = new THREE.BoxGeometry( 1, 1, 1 ); @@ -348,6 +364,13 @@

Credits

} + function checkMobile() { + + // Detect Safari browser + isMobile = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ); + + } + function setScroll() { const scrollX = window.scrollX; @@ -359,19 +382,29 @@

Credits

cameraDepth ); - if ( positioningMode === 1 ) { + if ( isMobile ) { + // Absolute positioning for mobile/iOS containerBg.style.position = 'absolute'; containerFg.style.position = 'absolute'; + containerBg.style.width = '100%'; + containerFg.style.width = '100%'; + containerBg.style.height = '100%'; + containerFg.style.height = '100%'; containerBg.style.transform = `translate(${scrollX}px, ${scrollY}px)`; containerFg.style.transform = `translate(${scrollX}px, ${scrollY}px)`; } else { + // Fixed positioning for desktop containerBg.style.position = 'fixed'; containerFg.style.position = 'fixed'; - containerBg.style.transform = 'translate(0px, 0px)'; - containerFg.style.transform = 'translate(0px, 0px)'; + containerBg.style.width = '100vw'; + containerFg.style.width = '100vw'; + containerBg.style.height = '100vh'; + containerFg.style.height = '100vh'; + containerBg.style.transform = ''; + containerFg.style.transform = ''; } @@ -426,6 +459,7 @@

Credits

rendererFg.setSize( width, height ); forcePixelsPerMeter(); + updateCubePosition(); recomputeElementBoxes(); setScroll(); @@ -457,12 +491,6 @@

Credits

updateDebug(); break; - case 'KeyP': - positioningMode = positioningMode === 0 ? 1 : 0; - setScroll(); - updateDebug(); - break; - } } @@ -470,17 +498,12 @@

Credits

function updateDebug() { const modes = [ 'Background only', 'Foreground only', 'Combined' ]; - const positions = [ 'Fixed', 'Absolute' ]; document.getElementById( 'debug' ).textContent = - `Mode: ${modes[ renderingMode ]} | Position: ${positions[ positioningMode ]}`; + `Mode: ${modes[ renderingMode ]}`; } - function animate( time ) { - - // Rotate cube for visual interest - cube.rotation.x = time * 0.0005; - cube.rotation.y = time * 0.001; + function animate() { setScroll(); render(); From 31b46c20256ee1fe6a57286d6d914e768b73577e Mon Sep 17 00:00:00 2001 From: Johnathon Selstad Date: Fri, 23 Jan 2026 21:13:48 -0800 Subject: [PATCH 03/12] Make Pretty with Claude --- examples/webgl_overlay.html | 1167 +++++++++++++++++++++++++++++------ 1 file changed, 963 insertions(+), 204 deletions(-) diff --git a/examples/webgl_overlay.html b/examples/webgl_overlay.html index cb83f7ffb69500..095d92d2ec5b09 100644 --- a/examples/webgl_overlay.html +++ b/examples/webgl_overlay.html @@ -5,12 +5,33 @@ + + + +
+
+
-
-

WebGL Overlay

-

- This example demonstrates a technique for rendering three.js canvases - that integrate with scrollable HTML content. 3D elements can appear both - in front of and behind HTML elements. -

- -

How It Works

-

- The overlay system uses two separate WebGL renderers: one positioned behind - the HTML content (background) and one in front (foreground). By adjusting - the camera's near and far clipping planes for each render pass, objects - closer than a threshold depth render in the foreground, while objects - further away render in the background. -

-

- The camera position is synchronized with the scroll position of the page, - creating the illusion that the 3D scene exists in the same coordinate - space as the HTML document. A forced pixels-per-meter calculation ensures - consistent sizing across different viewport dimensions. -

- -

Element Mapping

-

- HTML elements can be mapped to 3D representations. In this demo, paragraph - elements are visualized as wireframe boxes in 3D space, allowing for - potential interaction between 3D objects and page content. -

- -

Keymappings

- - - - - - - - - - -
ActionKey
Toggle Rendering Mode1, 2, 3
-

- Press 1 for background only, 2 for foreground only, - or 3 for combined foreground and background rendering. -

+
+

OVERLAY

+

Seamless 3D + HTML Integration

+
+ Scroll to explore +
+
+
-

Applications

-

- This technique enables creative web experiences where 3D graphics seamlessly - blend with traditional HTML layouts. Use cases include decorative page - elements, interactive visualizations embedded in articles, and immersive - scrolling experiences. -

+
+
+ +

Blending Dimensions

+

+ This technique enables seamless integration of WebGL 3D graphics + with traditional HTML content. Objects can appear both in front of and behind HTML elements, + creating immersive experiences that blur the line between web content and 3D worlds. +

+

+ The system uses dual WebGL renderers with synchronized camera positioning, allowing + 3D elements to exist in the same coordinate space as your scrollable document. +

+
+ +
+ +

Technical Features

+
+
+
+

Dual Renderer System

+

Background and foreground canvases with depth-aware clipping for proper occlusion.

+
+
+
+

Scroll Synchronization

+

Camera position tracks page scroll, maintaining spatial coherence.

+
+
+
+

Element Mapping

+

HTML elements can be represented and interacted with in 3D space.

+
+
+
+

Pixel-Perfect Scale

+

Forced pixels-per-meter ensures consistent sizing across viewports.

+
+
+
+ +
+ +

How It Works

+
+
// Split rendering by depth threshold
+const depthThreshold = 5.0;
+
+// Background pass: objects beyond threshold
+camera.near = depthThreshold;
+camera.far = 1000;
+rendererBg.render(scene, camera);
+
+// Foreground pass: objects closer than threshold
+camera.near = 2.0;
+camera.far = depthThreshold + 0.01;
+rendererFg.render(scene, camera);
+
+

+ By adjusting near and far clipping planes between render passes, objects automatically + sort themselves relative to the HTML layer without manual depth management. +

+
+ +
+ +

By The Numbers

+
+
+
2
+
Render Passes
+
+
+
60
+
FPS Target
+
+
+
0
+
Z-Fighting
+
+
+
+ +
+ +

Keyboard Shortcuts

+

Toggle between rendering modes to see how the overlay system works.

+
+
+ 1 + Background only +
+
+ 2 + Foreground only +
+
+ 3 + Combined view +
+
+
+ +
+ +

Use Cases

+

+ Product showcases where 3D models float alongside specifications. + Interactive articles with embedded visualizations. + Portfolio sites that blend creativity with content. + Data dashboards with dimensional charts that extend beyond the screen. +

+

+ The technique works anywhere traditional web layouts need a touch of dimensional magic + while preserving accessibility and SEO benefits of semantic HTML. +

+
+
-

Credits

+ -
Scroll to see 3D elements interact with the page.
-
+
MODE: Combined