webdevpuneet.com

Three.js Scroll Voxel Tower Build — GSAP Block Drop

Three.js Scroll Voxel Tower Build · Scroll · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="vxl-stage" id="vxlStage">
  <div class="vxl-intro"><p>Scroll ↓ to build the tower</p></div>
  <canvas id="vxlCanvas"></canvas>
  <div class="vxl-hud"><span id="vxlCount">0</span> / <span id="vxlTotal">0</span> BLOCKS</div>
</section>
<section class="vxl-bottom"><p>Topped out.</p></section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Data-driven voxel plan: hollow ring courses with setbacks, spire, and wings — swap the plan, keep the engine
Hollow-shell optimization: interior never-visible blocks are never created
Per-block scroll slices replace hundreds of tween objects with pure arithmetic
Seeded within-course shuffle lands blocks in mason order, deterministic across reloads
easeOutBack landing overshoot reads as a settle bounce with zero physics simulation
Scale pop-in over each slice's first third hides mid-air block appearance
Crane camera: height and look-at derived from the same progress as the build front
HUD counts actually-landed blocks, not raw scroll percentage

About this UI Snippet

How to Build a Scroll-Driven Voxel Tower With Three.js and GSAP

Screenshot of the Three.js Scroll Voxel Tower Build snippet rendered live

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.

Step by step

How to Use

  1. 1
    Load the three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js in that order before the snippet JS.
  2. 2
    Paste HTML, CSS, and JSA pinned scene shows an empty ground plate with a 0 / n BLOCKS HUD; the camera drifts slowly around it.
  3. 3
    Scroll to buildBlocks rain down course by course in scattered mason order, each landing with an easeOutBack settle as the counter climbs.
  4. 4
    Follow the craneThe camera rises with the build front and swings 1.4 radians around the tower, revealing setbacks, spire, and wings in profile.
  5. 5
    Scroll back upBlocks launch back into the sky in exact reverse order — per-block slices are pure functions of the scrubbed value.
  6. 6
    Build 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

Construction and real-estate landing pages
A tower assembling as you scroll is the literal pitch for developers, contractors, and proptech — cap it with a scroll year timeline of project milestones.
Voxel and sandbox game promos
Minecraft-adjacent aesthetics built from the same BoxGeometry blocks players recognize; swap the plan for your game's landmark.
Brand logo build-ups
Replace the tower plan with voxel coordinates of a logo so the mark constructs itself, an alternative to 2D scroll tile assemble.
Teaching data-driven scene construction
Cleanly separates the what (a coordinate plan) from the how (slices, easing, camera), a pattern worth copying into any generative scene.
"We build things" agency heroes
The mason-order landings and crane camera give a craftsmanship feel that suits studios and engineering consultancies.
Progress and fundraising visualizations
Bind blocks placed to a real metric — each block a donation or shipped feature — with the HUD showing true counts like the gates in portal gate.

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().