webdevpuneet.com

Three.js Scroll Shatter & Assemble — Crystal Debris Effect

Three.js Scroll Shatter & Assemble · Scroll · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="shx-stage" id="shxStage">
  <div class="shx-intro-overlay"><p>Scroll ↓ to assemble the shattered crystal</p></div>
  <canvas id="shxCanvas"></canvas>
  <div class="shx-hud"><span id="shxPct">0</span>% assembled</div>
</section>
<section class="shx-bottom"><p>Whole again.</p></section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), 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

Fragment targets sampled from a throwaway THREE.IcosahedronGeometry's face data — never added to the scene itself
Both position (lerpVectors) and rotation (slerpQuaternions) interpolate per fragment for physically correct tumbling
Fragments fling outward along their own target's radial direction, reading as a centered explosion, not random noise
MeshStandardMaterial with metalness/roughness and flatShading for faceted glass-crystal shading, not flat silhouettes
Three-light rig (violet point key light, cyan point rim light, soft directional fill) makes PBR shading read clearly
Height-based HSL hue gradient across fragments for a cohesive violet-to-cyan finished crystal
Residual per-fragment spin damps to zero as eased progress approaches 1, selling physical settle-in weight
Fully reversible and pinned — scrolling up replays the shatter with zero extra code

About this UI Snippet

How to Build a Scroll-Driven Shatter-to-Assemble Crystal With Three.js

Screenshot of the Three.js Scroll Shatter & Assemble snippet rendered live

The Three.js Scroll Shatter & Assemble snippet starts with ninety glowing crystal fragments flung apart in space and, as the visitor scrolls through a pinned stage, flies every fragment inward until it locks into an exact position and orientation on the surface of an intact icosahedron. Unlike a flat wireframe effect, this one leans on real MeshStandardMaterial shading and multiple point lights so the debris reads as solid, faceted crystal rather than a silhouette.

Deriving fragment targets from a reference geometry

Rather than hand-placing ninety fragments, the snippet builds a throwaway THREE.IcosahedronGeometry purely as a source of face data — it is never added to the scene. For each triangular face, the three vertex positions are averaged into a face center, and that center (plus its normalized "outward" direction) becomes the target position and target orientation for one small THREE.TetrahedronGeometry fragment. This is the same trick used by the scroll particle assembly snippet — sample a reference shape's geometry data to generate per-piece targets — applied here to solid mesh fragments instead of points.

Position AND rotation both need lerping

A shattered object is not just displaced, it is also tumbled — each piece has a random orientation while airborne and a specific, deliberate orientation once seated on the assembled surface. Interpolating position alone would leave fragments sliding into place while still spinning at some random fixed angle, breaking the illusion entirely. The snippet therefore stores both a startQuat and targetQuat per fragment and calls quaternion.slerpQuaternions(startQuat, targetQuat, eased) every frame alongside position.lerpVectors. Quaternion slerp, not a naive linear interpolation of Euler angles, is required here — lerping Euler angles independently produces gimbal-locked, non-uniform rotation speed and can even take the visually wrong short-path/long-path turn, whereas slerp guarantees the shortest, constant-angular-velocity rotation between the two orientations.

Flinging fragments outward, not randomly

Each fragment's scattered starting position is generated by taking its own target's outward direction vector and multiplying it by a large random distance, then adding a small random offset. Flinging pieces outward *along their own final position's radial direction* — rather than to a fully random point in space — makes the shatter read as an explosion centered on the object, which looks physically plausible, instead of a random cloud that happens to coincide with a crystal shape once settled.

Why MeshStandardMaterial and three lights, not flat/wireframe

Wireframe or MeshBasicMaterial looks are cheap but read as flat outlines with no sense of solidity — fine for a tunnel's glowing walls, wrong for debris meant to look like heavy crystal. This snippet uses MeshStandardMaterial with metalness and roughness tuned for a glassy-metallic look, plus flatShading: true so each tetrahedron's facets catch light distinctly rather than smoothing into a sphere. Because a PBR-ish material only reacts to real lights, the scene adds an ambient fill, a violet PointLight as the key light, a cyan PointLight as a rim light from the opposite side, and a soft white DirectionalLight — without those, the fragments would render as flat black shapes no matter how correct their geometry is.

Height-based hue gradient for cohesion

Each fragment's base color and emissive color are derived from a hue computed off its target's Y position (0.68 + center.y / SHAPE_RADIUS * 0.12), giving the assembled crystal a subtle violet-to-cyan gradient from bottom to top rather than every facet being an identical flat color. This mirrors the gradient technique used for particle color in the galaxy formation snippet, adapted here from a radius-based gradient to a height-based one since the target shape is a solid rather than a disk.

Residual spin that fades with progress

To avoid pieces looking like they simply teleport-slide into their slots, each fragment carries a small constant per-frame spin (frag.spin) that is multiplied by (1 - eased) — meaning fragments tumble energetically while far from home and that tumble smoothly damps to zero exactly as they arrive, adding a sense of momentum bleeding off rather than an abrupt stop. Combined with the whole scene slowly orbiting once eased climbs, similar to the reveal rotation in crystal cluster, the finished crystal gets a satisfying weighty settle instead of a mechanical snap.

Step by step

How to Use

  1. 1
    Load all three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js from the CDN panel, in that order.
  2. 2
    Paste HTML, CSS, and JSNinety scattered crystal fragments appear, lit by point lights, inside a pinned 3D stage with a live "% assembled" read-out.
  3. 3
    Scroll downFragments fly inward, their tumble damping out as position and rotation both lerp into an intact glowing icosahedron.
  4. 4
    Scroll back upThe crystal shatters back apart exactly in reverse, since every fragment lerps between two fixed start/target transforms.
  5. 5
    Retune the lookAdjust FRAGMENTS for a coarser or finer shatter, or SHAPE_RADIUS to resize the assembled crystal.
  6. 6
    Adjust the pacingChange the ScrollTrigger end value (+=450%) for a slower dramatic assembly or a quicker snap-together.

Real-world uses

Common Use Cases

Product reveal pages
Introduce a gemstone, watch, or premium hardware product by having it visibly assemble from fragments as the page scrolls.
Loot and collectible showcases
A shatter-to-assemble crystal fits RPG item reveals, trading-card sites, and NFT gallery hero sections.
Brand and agency intros
A dramatic assembly sequence signals craftsmanship and precision better than a static logo animation.
Generative and geometric art
Pair with a galaxy formation particle intro so the page opens with dust and closes with solid form.
Teaching quaternion interpolation
A concrete example of why slerpQuaternions is required over lerping Euler angles for correct rotation blending.
Portfolio hero pieces
Use the assembled crystal as a striking centerpiece, transitioning from or into a scroll tunnel journey.

Got questions?

Frequently Asked Questions

A real shattered object tumbles while airborne and settles into a specific orientation once seated. If only position were lerped, every fragment would slide smoothly to its slot while still spinning at whatever random angle it started at, which breaks the illusion of a piece locking into place. Interpolating rotation via slerpQuaternions alongside position lerp means both translation and orientation resolve together.

Lerping Euler angle components independently can produce gimbal lock, uneven rotation speed, and sometimes rotates the long way around instead of the shortest path. THREE.Quaternion.slerpQuaternions interpolates along the shortest arc on the rotation sphere at constant angular velocity, which is the mathematically correct way to blend between two 3D orientations.

Manually authoring ninety fragment positions and orientations that plausibly cover a polyhedron's surface would be tedious and error-prone. Building a reference IcosahedronGeometry (never added to the scene) and reading its triangular face centers and normals gives evenly distributed, geometrically correct target transforms for free, keyed off THREE's own subdivision.

MeshStandardMaterial implements physically-based shading, so it only produces visible color where light actually reaches the surface — unlike MeshBasicMaterial, which ignores lighting entirely. The ambient light provides a shading floor, the violet PointLight acts as a key light for directional highlights, the cyan PointLight rims the far side so fragments do not go fully black in shadow, and the soft DirectionalLight adds an overall fill, together making the flat-shaded facets read as solid crystal.

Yes. Click JSX for a React component, Vue for a Vue 3 SFC, Angular for a standalone component, or Tailwind for a React + Tailwind version. Build the fragment meshes, lights, and GSAP ScrollTrigger inside a mount effect keyed to a canvas ref, and on unmount kill the ScrollTrigger instance (or revert a gsap.context), dispose each fragment's geometry and material, and call renderer.dispose() so lights, geometry, and the WebGL context do not leak between route changes.