Skip to content
animorio
IT
Back to the library

The turning mosaic

Hover, TransitionsGSAP, CSS onlyMediumCompressed weight: Demo 9.3 KBGSAP 28 KBFreeDownload the zip

A wall of ceramic tiles in CSS 3D: under the pointer the tiles turn over in a wave and show another picture on their back, then swing home with a bounce. A click turns the whole wall in a wave that starts where you clicked.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The turning mosaic</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/dm-sans@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="studio">
    <header class="bar">
      <a class="brand" href="#">Saltmarsh Clay Lab</a>
      <nav class="bar__nav" aria-label="Sections">
        <a href="#">Classes</a>
        <a href="#">Kiln days</a>
        <a href="#">Tiles</a>
      </nav>
    </header>

    <!-- the wall: one block per tile, built by the script. Each tile has a front, a back and four clay edges -->
    <section class="wall-frame" aria-label="A wall of glazed tiles">
      <div class="wall" data-wall></div>
    </section>

    <p class="hint" data-hint>Move over the wall. Click to turn it all.</p>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

24 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The turning mosaic” from Animorio and fit it into my site

What it does

The page of a made-up ceramics studio, Saltmarsh Clay Lab, is almost all a wall of square tiles with sand-coloured grout. On the front the tiles make a blue and white pattern: each tile carries a quarter of a star, and four tiles side by side, mirrored, make a whole star with a yellow heart; small blue flowers bloom where the opposite corners meet. When the page opens the tiles arrive from the middle out, dropping onto the wall with a little bounce, and straight away the wall turns by itself, in rings that run out from the middle: behind it there is a second picture, a terracotta glaze with a big striped vase and the title “Fired by hand.”; then it turns back. Moving the pointer over it turns the nearby tiles in 3D, the one under the pointer first and then the others, and each shows its own piece of the picture behind, like a window that follows the pointer. While they turn they come off the wall a little, lean towards the pointer, darken edge-on and show their edge of fired clay; a gleam of glaze slides across them. When the pointer moves on they wait a moment and swing back on a spring: past the flat position, back again, and settle. A click turns the whole wall in a wave that starts from the point you clicked and shows the whole picture behind; then the pointer opens a window onto the front. Another click turns it back.

How it works

A tile is a little box

Each tile is a div with transform-style: preserve-3d holding five pieces: the front, pushed forward by half the thickness (translateZ), the back, turned 180 degrees and pushed back, and four clay-coloured edges turned 90 degrees on the sides. Front and back have backface-visibility: hidden, so we always see the face turned towards us. The wall has perspective: 1500px, which gives every tile the same vanishing point: the ones near the edges turn a little askew, like on a real wall.

Two pictures, one per face

The front is a quarter of a star drawn in SVG, centred on its bottom-right corner; the script copies it four times, mirrored with scale(-1, 1) and scale(1, -1), into a block of two tiles by two, and turns that into a single image (a blob: address). Every tile uses it as a background twice its size and shows its own quarter: so four tiles make a whole star, and that is why there is always an even number of columns and rows. The back is one picture as big as the wall, painted once on a canvas (vase, rings, a title measured with measureText so that it fits) and set as the background of every back (a blob: address too), moved with background-position by how far the tile is from the corner.

The wave under the pointer

On every move the distance between the pointer and the centre of each tile is measured, in tiles. Those within RADIUS turn with a delay that grows with the distance (WAVE seconds per tile): that is what makes the wave. The direction of the turn depends on the side: right of the pointer they turn one way, left of it the other, so the pointer seems to push them away. When a tile leaves the radius (with a small margin, so it does not flicker at the edge) a gsap.delayedCall of LINGER seconds brings it back; if the pointer returns first, it is cancelled.

The turn and the way back

A tile's state is an object with three numbers: the turn (ry), the lean (rx) and how far it comes off the wall (z). GSAP animates them and on every frame a function writes the transform and two CSS variables: --dim darkens the face when it is edge-on (1 − |cos ry|) and --sheen moves the gleam. The way out uses back.out, which goes a little too far; the way back elastic.out, which swings past flat a few times, less and less. Coming off the wall and the lean are keyframes 0, up, 0, and while it turns the tile gets a higher z-index, so it passes over its neighbours.

The click and the opening

A click changes the face of the whole wall and turns each tile with a delay equal to its distance from the clicked point times WAVE: a wave in rings. The pointer then always shows the other face, so on the turned wall it opens a window onto the front. While the wave runs the pointer waits (quietUntil), then its place is checked again. When the page opens the tiles drop from the middle with back.out, and the same wave starts by itself twice, there and back: so you see at once that there is something behind.

Values to change

NameFileDefaultWhat it changes
TILEscript.js76A tile's side in pixels. On a narrow screen it shrinks so that at least six fit on a row.
GAPscript.js4The grout between the tiles, in pixels.
RADIUSscript.js2.2How far from the pointer the tiles turn, in tiles: 1 turns little more than the one underneath, 4 opens a big window.
WAVEscript.js0.05The delay between one ring of tiles and the next, in seconds per tile: higher, the wave is slower and easier to see.
LINGERscript.js0.35How many seconds a tile stays turned after the pointer has gone: high, the pointer leaves a trail.
BOUNCEscript.js0.55How far the tile overshoots and bounces on its way back (0 = it just stops, 1 = very springy).
LIFTscript.js26How far a tile comes off the wall towards you while it turns, in pixels.
TITLEscript.js['Fired', 'by hand.']The title on the back of the wall, one line per entry: it grows as big as the space allows.

Try them in the Playground

When to use it

For the opening page of people who work with surfaces: a ceramics studio, a tile shop, an architect, a museum, a wallpaper brand. It works well as a reveal: the pattern in front, a message or a product behind. With tiles from 50 to 100 pixels; smaller, the turn becomes a flicker.

On phones

The same animation with the sizes adapted: the tiles get smaller so that six fit on a row, the wall stands upright and on the back the vase sits above the title. Dragging a finger over the wall turns the tiles as under the pointer, and they come back when you lift it; a tap turns the whole wall.

With reduced motion

The tiles do not turn or move: the back fades in over the front, with the same wave of delays, and fades out the same way. When the page opens the tiles fade in.

Pitfalls

  • Darkening and glinting the faces with two CSS variables (--dim, --sheen) changed on every tile at every frame made the browser restyle the whole wall: over a second in every three just for that, and the wave stuttered. Now the script writes opacity and background-position straight onto the shade and gloss elements, and the pictures are short blob: addresses, not data: ones a megabyte long.
  • Hundreds of tiles always in 3D, each with six layers, weigh on the browser even standing still: a tile at rest goes flat (transform-style: flat, no edges, only the face it shows) and becomes a little box again only while it turns.
  • The back picture made as one SVG, shown in every tile with a use and a different viewBox, gave big letters that did not line up from one tile to the next: the text was laid out a little differently in each piece. Painted once on a canvas and used as a shifted background, it always lines up, and it is lighter too.
  • Front and back at the same depth with no turn (in the version without motion) fought over which was in front and the back did not show: without motion the tile goes flat (transform-style: flat) and the back fades in on top.
  • Always turning towards +180 made every tile turn the same way: the target angle is searched for in the right direction (the first multiple that shows the wanted face, to the right or to the left), and at the end it is brought back between −180 and 180, so that turn after turn does not pile up thousands of degrees.
  • The way back restarting when the pointer comes back halfway: the tile's tweens are killed and it starts again from the angle it is at, towards the nearest face; and a tile already heading for the right face is left alone, or every mouse move would restart it.
  • At the edge of the radius a tile went in and out at every pixel: to go back it has to be another third of a tile further away.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, DM Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 9.3 KB; the libraries: GSAP 28 KB.

Testing

Tested on 10 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All animations