lottery
🎉🌟✨🎈年会抽奖程序,基于 Express + Three.js的 3D 球体抽奖程序,奖品🧧🎁,文字,图片,抽奖规则均可配置,😜抽奖人员信息Excel一键导入😍,抽奖结果Excel导出😎,给你的抽奖活动带来全新酷炫体验🚀🚀🚀
Real HTML rendered through a WebGPU + TSL pipeline — DOM as a texture source with live post-processing.
git clone https://github.com/felixturner/html-in-canvas-cracks.gitfelixturner/html-in-canvas-cracksReal HTML, real layout, real scrolling — rendered through a WebGPU pipeline with TSL shaders, feedback trails, and a live 3D code tunnel in the background. Click the page to crack it; click again to shatter chunks off into the tunnel. A small experiment in treating the DOM as a texture source rather than the final display surface.
Warning
Requires desktop Chrome 141+ with
chrome://flags/#canvas-draw-element
enabled.
Modern browsers can paint a live, laid-out DOM element straight onto a 2D
canvas via the new CanvasRenderingContext2D.drawElement()
API. Once the page is a canvas, it's a texture. Once it's a texture, every
trick WebGPU knows is on the table.
This project wires that pipeline up end-to-end:
three.js WebGPURenderer pulls that canvas as a CanvasTexture and
composites it through a multi-pass node-material pipeline.Each frame goes through a few render targets before hitting the screen:
PostProcessing with a TSL outputNode layers the
feedback RT and the foreground RT together through a BloomNode. Bloom is
scoped to the background scene so the HTML stays crisp at its native
resolution.Everything uses half-float RTs in linear sRGB — the color math works in the right space and bloom doesn't get blown out by the tone-mapper.
Shaders are written as node graphs with three.js TSL
(three-shader-language) rather than strings of WGSL. The pre-crack page is
a MeshBasicNodeMaterial with a UV-sampled CanvasTexture. The tunnel's
code planes are distance-faded against the scene fog. Shards use a TSL
distance fog of their own that fades alpha (not color) toward the far
plane, so a receding shard reveals the tunnel behind it instead of painting
an opaque black quad over it.
When you click the page, it cracks.
src/shatter.js runs the crack lines through a DCEL face-walker to split
the hit polygon into sub-polygons, then earcut-triangulates each one.SHARD_FOG_FAR their TSL
fog alpha hits zero and they unblock the tunnel behind them.growDelay attribute and a single progress uniform.Shards live in viewport-CSS-pixel space. shardRoot applies a uniform scale
of 2/vh with a negative Y (so VP-y-down maps cleanly to world-y-down) and
is anchored at the top-left of the frustum. A crack clicked at (300, 200)
stays at (300, 200) after any resize — the HTML texture re-flows through
the fixed shard outline as the document relays out.
UVs are computed live in the vertex shader:
vpPx = positionLocal.xy + centroid
docY = vpPx.y + scrollY
uv = vec2(vpPx.x / sourceCssW, 1 - docY / sourceCssH)
So each shard is effectively a "fixed window onto the live DOM" — scrolling the page reflows content behind the cracks without the cracks themselves moving.
The background is a tunnel of floating code snippets. The source strings are
imported verbatim from the project itself via Vite's ?raw import, so the
background is literally the code that renders it.
CanvasTexture and a MeshBasicNodeMaterial
with custom glyph tinting.Roughly 200 planes live at any moment. They drift forward toward the camera,
get recycled behind it, and smoothly fog out via
THREE.Fog(0x000000, near, far). Because the tunnel renders into the
feedback RT, each glyph leaves a fading contrail that reinforces the depth
illusion.
A mouse-parallax tilt is wired to the tunnel group: normalized cursor
coordinates drive a lerped rotation with a small TUNNEL_TILT_MAX cap, so
the background responds to the cursor without drifting into full 3D-camera
territory.
The clickable layer is the real DOM (#source), not the 3D mesh. That keeps
text selection, link hovering, and accessibility behavior working without
any custom raycasting. The 3D composite (#stage) sits on top with
pointer-events: none, so input passes through to the canvas beneath.
Treating the DOM as a texture while keeping it scrollable turns out to have a few subtleties.
#stage is position: fixed, so the browser pins the
canvas to the viewport on the compositor thread. An earlier design used
position: absolute with a per-frame transform: translateY(scrollY)
compensation — that made VP-locked shards appear to shift vertically
during scroll before snapping back once the main thread caught up. Fixed
positioning makes that problem vanish.CanvasTexture in WebGPU doesn't
re-allocate its GPU texture when the backing canvas's width/height
change — it silently scales the new content into the old-sized
allocation. paint() detects a buffer-dimension change and calls
htmlTexture.dispose() (preserving the JS object so shard materials'
TSL texture() refs keep pointing at the right node), forcing the
renderer to allocate a fresh GPUTexture at the new size on the next
render.screenReadyU uniform), so the
foreground RT has alpha = 1 from frame zero and the tunnel can never
bleed through while the pipeline warms up. body flips to ready on
the first successful pipeline frame, which reveals #source and fades
in the native DOM layer underneath.Keyboard:
x — explode remaining shards outward into the tunnel.r — reset shard positions to their original slots.t — apply a small random tilt to each shard.o — toggle OrbitControls on the camera (for debugging).Add ?dev to the URL for the debug overlay, Stats panel, and two draggable
split dividers that slice the viewport into three panes — GPU composite,
source canvas, and raw HTML — so you can see each stage of the pipeline
side-by-side.
?raw importsnpm install npm run dev
src/main.js — pipeline setup, render passes, shard lifecycle, scroll/resize wiringsrc/shatter.js — crack-graph-to-polygons face walker built on earcutsrc/tunnel.js — background code tunnel (text quads, fog, recycling)src/sfx.js — small WebAudio utilitiessrc/page.css — styling for the DOM layer that becomes the textureMIT
more like this
🎉🌟✨🎈年会抽奖程序,基于 Express + Three.js的 3D 球体抽奖程序,奖品🧧🎁,文字,图片,抽奖规则均可配置,😜抽奖人员信息Excel一键导入😍,抽奖结果Excel导出😎,给你的抽奖活动带来全新酷炫体验🚀🚀🚀
🎥🤟 8 minimalistic templates for tfjs mediapipe handpose and facemesh
A repository about starting from scratch for monorepo, fastify, Phaser, colyseus, and threejs.
search projects, people, and tags