Three.js Scroll Voxel Tower Build — GSAP Block Drop
Three.js Scroll Voxel Tower Build · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
How to Build a Scroll-Driven Voxel Tower With Three.js and GSAP

The Three.js Scroll Voxel Tower Build snippet drops a few hundred voxel blocks out of the sky, one after another, assembling a tapering tower with setbacks, a spire, and side wings as the user scrolls — each block popping in with an easeOutBack overshoot while a crane-style camera tracks the rising build front. GSAP's ScrollTrigger scrubs one progress value; every block simply owns a slice of it.
A voxel plan, not hardcoded geometry
The tower is described as data before any mesh exists: a ring() helper pushes hollow square courses of grid coordinates into a plan array, called with shrinking half-widths (4, then 3, then 2) to produce classic skyscraper setbacks, topped by a four-block spire and flanked by two small wings. Because the plan is plain coordinates, swapping in any other voxel shape — a logo, a character, letters — means replacing the plan-building loop and nothing else. The hollow-ring optimization also matters: interior blocks that would never be visible are simply never created, keeping the mesh count to the visible shell.
Per-block scroll slices instead of tweens
With hundreds of blocks, creating one GSAP tween per block would work but allocates hundreds of tween objects for what is pure arithmetic. Instead, block i maps its local progress from the global value as (p − start) / win, where start distributes blocks evenly across the scroll and win is sized so roughly fourteen blocks are airborne at any moment. This is the same slice pattern that drives the domino run and the peel in the exploded view — a stagger that is a pure function of one scrubbed number, hence perfectly reversible.
Mason-order shuffling within each course
Blocks sort bottom-up by level, but within a level they shuffle using a seeded sin-hash — the same deterministic randomness technique as the Rubik's cube assembly. Without the shuffle, each course would sweep in reading order like a printer; with it, blocks land scattered around the ring like a mason laying bricks, while determinism keeps every scroll-up/scroll-down cycle identical.
easeOutBack landings and scale pop-in
Each falling block interpolates from a randomized drop height down to its slot through easeOutBack, whose characteristic overshoot dips the block a hair below its rest position and settles it back up — reading as a bounce without running any physics. Simultaneously the block's scale ramps from near-zero over the first third of its slice, hiding the visual pop of a box appearing mid-air. Blocks outside their slice sit at scale 0.001 rather than visible = false, so toggling requires no scene-graph churn.
A crane camera that tracks the build front
The camera height and look-at target both derive from p × maxLevel, so the view cranes upward exactly as fast as the tower tops out, while a slow clock-driven orbit plus a scroll-linked 1.4-radian swing keeps the silhouette rotating through profile views. Fog matched to the background swallows the ground plate edges, focusing attention on the build. The HUD counts blocks whose slice has completed — the same honest arrival counting used by the portal gate sequence — so the number reflects blocks actually landed, not raw scroll percentage.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not need to hand-derive the slice math or voxel plan. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to explain how per-block scroll slices replace tweens, why the within-course shuffle must be seeded, or how easeOutBack fakes an impact settle. The same assistant can generate a new plan array from a description ("build a chess rook", "build the letters HI"), convert the blocks to a single InstancedMesh with per-instance matrices updated in the frame loop if you push the count into the thousands, or add a dust-puff sprite at each landing keyed to the exact frame a block's slice completes. Treat the code as a starting point to interrogate and reshape, not a finished artifact.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "scroll-driven voxel tower build" in plain HTML, CSS, and JavaScript using Three.js and GSAP's ScrollTrigger plugin, all loaded from a CDN (no bundler, no build step).
Requirements:
- A pinned full-viewport section with a canvas, WebGLRenderer, PerspectiveCamera (resized with aspect on window resize), ambient plus directional light, a cylinder ground plate, and Fog matched to the background color.
- A voxel plan as pure data first: a ring(cx, cz, halfWidth, y) helper that pushes hollow square courses of {x, y, z} grid coordinates (solid only at ground level), called with shrinking half-widths (4→3→2) for skyscraper setbacks, plus a 4-block spire and two small side wings. Interior blocks of upper floors must never be created.
- Sort the plan bottom-up by level, shuffling WITHIN each level via a seeded sin-hash (no Math.random) so courses fill in scattered mason order deterministically.
- One BoxGeometry block mesh per coordinate with a color from a small palette (seeded pick), initial scale 0.001, each storing its target Y and a randomized drop-from height ~14–24 units above.
- One GSAP tween (ease "none") scrubbing p 0→1 on a ScrollTrigger with pin: true and end ~+=500%.
- Per-block scroll slices in the frame loop: block i animates across [i/n × (1 − win), + win] where win keeps ~14 blocks airborne simultaneously; local progress drives scale pop-in over its first third and an easeOutBack fall from dropFrom to targetY.
- A crane camera whose height and lookAt target derive from p × maxLevel so it tracks the build front, plus a slow clock orbit and a scroll-linked ~1.4 radian swing.
- A HUD "placed / total BLOCKS" that counts blocks whose slice has completed (local progress ≥ 1), and an intro overlay fading once p passes 0.02.
- Confirm scrolling up launches blocks back skyward in exact reverse order.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Load the three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js in that order before the snippet JS.
- 2Paste HTML, CSS, and JSA pinned scene shows an empty ground plate with a 0 / n BLOCKS HUD; the camera drifts slowly around it.
- 3Scroll to buildBlocks rain down course by course in scattered mason order, each landing with an easeOutBack settle as the counter climbs.
- 4Follow the craneThe camera rises with the build front and swings 1.4 radians around the tower, revealing setbacks, spire, and wings in profile.
- 5Scroll back upBlocks launch back into the sky in exact reverse order — per-block slices are pure functions of the scrubbed value.
- 6Build your own shapeReplace the plan-building loops with any list of {x, y, z} grid coordinates — a logo, initials, a rook — and everything else adapts automatically.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
A staggered timeline plays; this scene scrubs. Scrubbing a timeline with hundreds of member tweens works but allocates a tween object per block and makes the stagger window awkward to reason about. Computing local progress as (p − i/n × (1 − win)) / win per frame is a few multiplications per block, keeps several blocks airborne at once via the window width, and reverses perfectly since it is stateless arithmetic.
The ring() helper only pushes coordinates where |x| or |z| equals the ring half-width — the hollow shell — except at ground level where the full plate is kept. Interior blocks of upper floors would never be visible from any camera angle, so they are never instantiated: for this plan that roughly halves the mesh count versus solid courses, directly cutting draw calls.
easeOutBack's cubic overshoots its target — the block travels slightly past its rest Y then settles back. Applied to a fall from above, the overshoot dips it into the slot and back up a fraction, which the eye reads as an impact settle. It is one easing function evaluated on the block's slice progress, versus integrating velocity and restitution per frame in a physics engine.
Both hide the block, but flipping visible would still be fine here; scale keeps the code branch-free since scale also animates the pop-in over the slice's first third. Unplaced blocks render as sub-pixel specks that the fog and distance hide completely, and the per-frame cost of a hidden-by-scale box is negligible at these counts.
Yes. Export via JSX, Vue, Angular, or Tailwind. Construct the plan, blocks, and ScrollTrigger in a mount effect against a canvas ref; drive the HUD counter through a ref, not state, since it changes every frame. On cleanup kill the ScrollTrigger, dispose each block's geometry and material (or share one BoxGeometry across all blocks to make disposal a single call), and call renderer.dispose().