Scroll Tile Assemble — Free GSAP Mosaic Snippet

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

Share & Support

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to trace the background-position slicing formula or the pseudo-random seed function by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the sin-based rand function produces a deterministic scatter, and why gsap.set displaces tiles with transforms instead of computing scattered grid positions directly. The same assistant can help optimize it — checking whether twelve simultaneously-tweened tiles with a random stagger stays cheap at higher tile counts, or whether the shared gradient slicing approach still holds up once a real photo is swapped in at a different aspect ratio. It is just as useful for extending the effect: ask it to support a non-uniform grid, add a sound or haptic cue as each tile lands, or let the user drag a tile out of place before it snaps back on release. Treat the code less like a finished artifact and more like a starting point for a conversation.

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:

text
Build a "scroll tile assemble" mosaic effect in plain HTML, CSS, and JavaScript using GSAP and its ScrollTrigger plugin (load both from a CDN, no build step).

Requirements:
- Generate a COLS by ROWS grid of tile elements in CSS grid, where every tile shares the exact same background-image (a gradient or photo) sized at COLS*100% by ROWS*100%, and each tile's background-position is set individually to (column / (COLS-1)) percent by (row / (ROWS-1)) percent, so that when all tiles sit in their grid cells the shared background reads as one continuous, seamless image.
- Before any animation runs, use gsap.set to displace every tile away from its grid position with a scattered x, y, rotation, and reduced scale, plus zero opacity — but the scatter offsets must come from a deterministic seeded formula (not Math.random), so the same scatter layout appears identically on every page load and every scroll reversal.
- Register a single GSAP timeline on a ScrollTrigger with pin: true and scrub, and inside it tween all tiles' x, y, rotation, scale, and opacity back to their natural (zero-offset) values using a stagger with from: random, so tiles fly into place in a shuffled, not row-by-row, order.
- Do not calculate the tiles' assembled pixel coordinates in JavaScript — the assembled positions must come entirely from the CSS grid layout, with GSAP only responsible for animating the transform offset away from and back to that layout.
- Add a heading/caption element that fades in only after most of the tiles have landed (near the end of the timeline), so it appears to sit on top of a completed surface.
- The whole sequence must reverse cleanly on scroll-up, shattering the assembled image back into its seeded scatter.

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

  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; the inverse dive is scroll grid zoom.
Product screenshots
Piece a dashboard together, then tour it with sticky scroll features.
Team or gallery walls
Fly portraits into a wall; browse them in a photo gallery.
"Coming together" stories
Metaphor sections for mergers or integrations inside a scroll pin story.
Portfolio openers
Assemble the hero, then a scroll reveal grid of work.
Puzzle campaigns
Literal jigsaw promos; add a confetti button at completion.

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.