Mosaic Image Reveal
Mosaic Image Reveal breaks your image into a grid of tiles that animate into place, scaling, fading, and optionally rotating in 3D, powered by GSAP 3 and ScrollTrigger.
Everything is painted on a single <canvas>, so it stays feather-light on the DOM: no extra DIVs, no per-tile elements, just one node covering the image. The original <img> remains untouched in your markup (hidden only during the reveal), keeping the page fully semantic and SEO-friendly.
The canvas mirrors the image’s rendered box at any size, so the mosaic re-tessellates on resize instead of cropping. And it respects accessibility: with prefers-reduced-motion: reduce, no canvas is created and no animation plays, the plain image simply displays as-is.




Instructions
Grid layout
cols — default: 8 The number of tiles the image is divided into horizontally. The whole grid is cols × rows tiles, so with the default you get 40 tiles. Values are rounded, and anything below 1 is clamped to 1. Higher numbers = smaller tiles = more detailed mosaic, but also more drawing work per frame.
rows — default: 5 Same idea, but vertically — how many tiles stack from top to bottom.
Reveal order
order — default: "center" Controls which tile starts animating first — i.e., the direction the reveal sweeps across the image. Passed straight to GSAP's stagger.from option. Accepted values:
"center"— tiles in the middle of the image animate first, spreading outward (ripple from the middle)."edges"— the opposite: border tiles go first, converging toward the center."random"— tiles fire in a random order, giving a scattered/dissolve feel."start"— begins at the first tile (top-left) and proceeds in reading order, left-to-right, top-to-bottom."end"— begins at the last tile (bottom-right) and runs in reverse reading order.
The actual spacing between tiles is controlled by stagger.
Transform start values (each animates to identity)
scaleFrom — default: 0.4 Each tile's starting size, as a multiplier. The tile animates from this value to 1 (full size). Range 0–2:
0— tiles start as invisible dots growing out.0.4(default) — tiles start at 40% size and scale up.1— no scaling at all; tiles are full-size from the start.> 1— tiles start oversized and shrink into place.
Note: a value ≤ 1 (with no rotation) also enables a performance optimization — tiles never paint outside their own grid cell, so only changed cells get repainted.
opacityFrom — default: 0 Each tile's starting transparency, animating to fully opaque (1). Range 0–1. 0 means tiles fade in from nothing; 1 means they're fully visible the whole time (no fade).
rotateXFrom — default: 0 Starting rotation around the X axis, in degrees, animating to 0. Positive values tip the image so the bottom swings toward the viewer. Crucially, the pivot is the center of the whole image, not each tile — so the entire mosaic rotates as one coherent 3D plane while the tiles individually fly in. Any non-zero value here (or below) switches tiles to the more expensive 3D mesh rendering path.
rotateYFrom — default: 0 Starting rotation around the Y axis, in degrees, animating to 0. Positive values turn the image so the right side swings away from the viewer. Same center-of-image pivot behavior as rotateXFrom.
perspective — default: 900 The camera distance (in px) used for the 3D projection, only when a rotation is set. Smaller values exaggerate the 3D effect (closer camera, more distortion); larger values flatten it toward a subtle tilt. Internally clamped to a minimum of 50 so the projection can't blow up. Ignored entirely if both rotation values are 0.
Timing
duration — default: 0.5 How long each individual tile's tween takes, in seconds. This is per tile, not the whole animation — total time depends on this plus the stagger.
stagger — default: 0.025 The delay, in seconds, between consecutive tiles starting their animation. With 40 tiles and the default, the last tile begins a full second after the first. Set to 0 and all tiles animate simultaneously.
ease — default: "power3.out" Any GSAP easing name. "power3.out" starts fast and decelerates smoothly into the final position — a natural "settle" feel. You could swap in "power4.out", "elastic.out", "back.out(1.7)", "none" (linear), etc.
Triggers
trigger — default: "click" How the reveal gets started. Two modes:
"click"— the canvas is made clickable (cursor becomespointer, and it's givenrole="button"+tabindex="0"so it's keyboard-operable via Enter/Space). Each click replays the animation."viewport"— uses the ScrollTrigger plugin to fire the animation automatically when the container scrolls into view, using thestart,end,once,scrub, andscrolleroptions below. If the ScrollTrigger plugin isn't loaded, it prints a warning and falls back to click mode.
start — default: "top 75%" (viewport mode only) A ScrollTrigger start position. "top 75%" means: trigger when the top of the container reaches 75% of the way down the viewport — i.e., shortly after the image becomes visible while scrolling.
end — default: "bottom center" (viewport mode only) A ScrollTrigger end position. Mostly relevant for scrub mode (below), where it defines the scroll distance over which the animation progress is mapped. "bottom center" means the animation completes when the bottom of the container reaches the vertical center of the viewport.
once — default: true (viewport mode, non-scrub only) When true, the ScrollTrigger fires a single time and then kills itself — the reveal plays once and stays done. When false, the trigger re-fires every time the element re-enters the start position (replaying the animation). Irrelevant in scrub mode, since scrubbing is continuous.
scrub — default: false (viewport mode only) Turns the animation into one that's driven by the scrollbar rather than a timer. Accepts:
false— normal time-based playback when the trigger fires.true— tile progress is directly tied to scroll position; scroll down = tiles assemble, scroll up = they disassemble.- A number (e.g.
0.5) — scrubbing with "catch-up": the animation follows the scroll but takes that many seconds to smooth out, giving a heavier, lagging feel.
Bonus behavior: in scrub mode, calling play() smooth-scrolls the scroller back to just before the trigger start, rewinding the reveal.
scroller — default: null (viewport mode only) By default ScrollTrigger watches the window/page scroll. If your page scrolls inside a custom container (e.g., an overflow-div or a smooth-scroll library wrapper), pass that element here so trigger positions are computed against the right scroller.
hideImage — default: true While the reveal is active, the original <img> is hidden (visibility: hidden) so you see the canvas tiles instead — without this, the full image would show through and the effect would be pointless. The previous visibility value is remembered and restored by destroy(). Set to false if you want the underlying image visible behind/under the tiles for some reason.
Accessibility
respectReducedMotion — default: true Honors the visitor's OS-level "reduce motion" preference (prefers-reduced-motion: reduce). When enabled and the visitor has requested reduced motion, the factory becomes a complete no-op: no canvas is created, no tween scheduled, and the plain <img> is simply displayed as-is. The returned stub still has the same shape (play(), destroy(), etc.) so your calling code doesn't need special guards — but reducedMotion will be true. Set to false only if you're certain you want to force animation on everyone.