webdevpuneet.com

Scroll Tile Assemble — Free GSAP Mosaic Snippet

Scroll Tile Assemble · Scroll · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="sta-top"><p>Scroll ↓</p></section>
<section class="sta-stage" id="staStage">
  <div class="sta-board">
    <div class="sta-mosaic" id="staMosaic"></div>
    <div class="sta-copy" id="staCopy"><h2>Pieces into place</h2><p>Twelve scattered tiles assemble into one panel.</p></div>
  </div>
</section>
<section class="sta-bottom"><p>Scroll up to shatter the panel back apart.</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

Shared-image slicing
background-position windows one gradient.
Seeded scatter
Deterministic hash, identical every load.
Grid as truth
Tweens return transforms to zero.
Shuffled stagger
Pieces arrive in random order.
Late headline
Copy lands once the surface exists.
Count-agnostic build
COLS and ROWS drive everything.
Compositor-only
Transforms and opacity, no layout.
Reversible shatter
Scrolling up explodes the mosaic.

About this UI Snippet

Scroll Tile Assemble — Scattered Pieces Fly Into One Panel on Scroll

Screenshot of the Scroll Tile Assemble snippet rendered live

The scroll tile assemble starts with twelve tiles thrown across the viewport — rotated, shrunken, half-faded — and as you scroll, each piece flies home into a 4×3 grid that fuses into one continuous panel, with a headline settling on top. Scroll up and the panel shatters apart again. This snippet builds it with GSAP ScrollTrigger (from a CDN), a seeded scatter, and a background-position slicing trick that makes separate tiles read as a single image.

One gradient, sliced across twelve tiles

Every tile shares the same background-image gradient at background-size: 400% 300% — four columns by three rows of the tile's own size. Each tile then shows its slice via background-position: (c/(COLS−1))% (r/(ROWS−1))%, the CSS percentage-positioning formula that maps a grid cell to its window onto the oversized background. When the tiles land, edges align perfectly and the grid reads as one unbroken picture. Swap the gradient for a url() image and you have a photo mosaic with zero markup changes.

The scatter is seeded, not random

Start positions come from sin(seed × 127.1) × 43758.55 % 1 — the classic shader-style hash — fed by each tile's index. It looks random but is fully deterministic: the scatter is identical on every load and every scrub-back, so users who scroll up see the exact shatter they came from. Math.random() would re-scatter on refresh and, worse, if setup ever re-ran, tiles would visibly jump.

GSAP animates from where they are to where they belong

The tiles are laid out by CSS grid in their *final* positions; gsap.set then displaces them with transforms (x, y, rotation, scale). The scrubbed tween simply returns everything to zero — meaning the grid layout is the single source of truth for the assembled state, and the browser handles all responsive sizing. No target coordinates are ever computed in JS.

A random-order stagger sells "pieces arriving"

stagger: { each: 0.06, from: 'random' } offsets each tile's start so pieces stream in from all directions in shuffled order rather than row-by-row — the difference between an assembling mosaic and a sorting animation. Because the offsets live inside one tween, the whole assembly still reads as a single gesture under the scrub.

The copy waits for a surface to land on

The headline fades in at 85% of the timeline, once enough tiles have fused to act as its backdrop. Text-shadows keep it legible over the gradient seams during the final moments of assembly.

Pinned, scrubbed, compositor-only

The stage pins for +=180% with scrub: 0.4; every animated property is a transform or opacity, so twelve simultaneously moving layers stay cheap, and will-change: transform promotes them before the first frame.

Why displacement transforms beat animating grid position

An alternative approach would animate each tile's actual grid-column/row from a scattered layout to the final one, but CSS grid placement isn't tweenable and would require manual absolute positioning with computed pixel targets per tile. Using gsap.set to displace tiles with transforms while CSS grid handles the real, responsive layout means the assembled state needs zero JavaScript-computed coordinates — resize the viewport and the "home" positions recompute for free, while the scatter offsets (in fixed pixels) stay proportionally sensible relative to the grid's own responsive sizing.

Customizing it

Change COLS/ROWS (slicing and scatter adapt automatically), widen the scatter range for a more explosive start, or point the shared background at a product screenshot. Related effects: the inverse scroll grid zoom, staggered entrances in scroll reveal grid, slat-based scroll blinds reveal, and a masonry grid for static layouts.

Step by step

How to Use

  1. 1
    Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
  2. 2
    Paste HTML, CSS, and JSTiles generate, slice the gradient, and scatter.
  3. 3
    Scroll into the stagePieces start flying home in shuffled order.
  4. 4
    Complete the assemblyEdges fuse and the headline settles on top.
  5. 5
    Scroll back upThe panel shatters along the same paths.
  6. 6
    Use a real imageSwap the gradient for url() — slicing is unchanged.

Real-world uses

Common Use Cases

Brand reveals
Assemble a logo or key visual from twelve scattered tiles, using background-position windows onto one shared gradient so the seams vanish.
Product screenshots
Piece a dashboard together, then tour it with scroll sticky features, with deterministic seeded scatter so every load looks identical.
Team and gallery walls
Fly portraits into a wall, then browse them in a photo gallery, with tiles arriving in a shuffled order.
Coming together stories
Use the metaphor for mergers or partnerships, followed by a scroll pin story to explain the journey.
Openers and puzzle campaigns
Assemble a portfolio hero before a scroll reveal grid, or add a confetti button to reward a completed puzzle.
Related: Three.js Scroll Möbius Strip Ride
See the Three.js Scroll Möbius Strip Ride for a related scroll pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Every tile paints the same background at background-size: 400% 300% — a canvas 4×3 tiles big — and offsets its window with the percentage form of background-position, (c/(COLS−1))% (r/(ROWS−1))%. Each tile therefore displays exactly its cell of the shared picture, so when transforms return to zero the seams align into one unbroken panel.

Start offsets come from a deterministic hash — sin(seed × 127.1) × 43758.55 % 1 — keyed by tile index. The layout looks random but is identical on every load and scrub, so reversing shows the exact shatter you arrived from, and re-running setup (hot reload, framework strict mode) can never make tiles jump to new positions.

It doesn't need to: CSS grid lays the tiles out in their assembled positions, and gsap.set displaces them with transforms. The scrubbed tween just animates x, y, rotation, and scale back to zero, so the grid remains the single source of truth and responsive resizing works automatically — no coordinates are computed in JavaScript.

Yes — change the tiles' background-image to url(your-image.jpg); background-size and the per-tile positions already do the slicing. Pick an image at the mosaic's aspect ratio (16/9 here) so nothing distorts, and consider a subtle gap: 0 to hide seams entirely once assembled.

CSS grid placement can't be tweened directly, so animating "real" grid positions would need manual absolute coordinates recomputed on every resize. Letting grid handle the assembled layout while GSAP only displaces tiles with transforms means resizing the viewport instantly gives correct home positions with no JavaScript involved — only the scatter's starting offsets are fixed pixel values, and those stay visually proportional at common viewport widths.

Render the tiles from a COLS×ROWS array with their backgroundPosition computed inline, then run the seeded set() and timeline in a mount effect — useEffect, onMounted, or ngAfterViewInit — inside gsap.context scoped to the mosaic ref, reverting on cleanup so the pin unregisters. The deterministic seed makes double-invoked effects safe. Grid and sizing translate straight to Tailwind utilities.