Skip to content
animorio
IT
Back to the library

The wooden tile title

Text, PhysicsGSAPAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

A cloth bag drops on the green table, opens and tips over: wooden tiles with engraved letters roll, bounce, turn face up and click into a rack spelling a phrase, which waves hello. A click shuffles them: the ones that are needed jump and spin in the air, the others go back into the bag.

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 wooden tile title</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage data-autoclic role="button" tabindex="0" aria-label="Shuffle the tiles">
    <!-- the phrase on the rack, for screen readers: the script keeps it in step with the tiles -->
    <h1 class="reader" data-reader>The Oak Room</h1>
    <!-- the table seen from above at an angle: the bag, the rack and the tiles live in a tilted 3D plane; every tile is a box made of divs -->
    <div class="world" data-world>
      <div class="bag" data-bag>
        <div class="bag__tip" data-bag-tip>
          <svg class="bag__svg" viewBox="0 0 200 220" aria-hidden="true">
            <defs>
              <radialGradient id="bag-cloth" cx="0.36" cy="0.42" r="0.75">
                <stop offset="0" stop-color="var(--bag-light)"/>
                <stop offset="0.55" stop-color="var(--bag)"/>
                <stop offset="1" stop-color="var(--bag-dark)"/>
              </radialGradient>
              <linearGradient id="bag-ruffle" x1="0" y1="0" x2="1" y2="0">
                <stop offset="0" stop-color="var(--bag-dark)"/>
                <stop offset="0.4" stop-color="var(--bag-light)"/>
                <stop offset="1" stop-color="var(--bag-dark)"/>
              </linearGradient>
              <pattern id="bag-weave" width="4" height="4" patternUnits="userSpaceOnUse">
                <path d="M0 1h4M1 0v4" stroke="var(--bag-dark)" stroke-width="0.6" opacity="0.35"/>
              </pattern>
            </defs>
            <path class="bag__body" data-bag-body fill="url(#bag-cloth)"/>
            <path class="bag__weave" data-bag-weave fill="url(#bag-weave)"/>
            <g class="bag__folds" data-bag-folds fill="none" stroke="var(--bag-dark)" stroke-linecap="round"></g>
            <g class="bag__patch" transform="rotate(-5 100 156)">
              <rect x="70" y="140" width="60" height="32" rx="5" fill="var(--patch)"/>
              <rect x="73.5" y="143.5" width="53" height="25" rx="3" fill="none" stroke="var(--bag)" stroke-width="1" stroke-dasharray="3 2.4"/>
              <text x="100" y="161.5" text-anchor="middle" font-size="12.5" font-weight="700" letter-spacing="1.4" fill="var(--bag-dark)">TILES</text>
            </g>
            <path class="bag__ruffle" data-bag-ruffle fill="url(#bag-ruffle)"/>
            <ellipse class="bag__mouth" data-bag-mouth fill="var(--bag-inside)"/>
            <path class="bag__lip" data-bag-lip fill="none" stroke="var(--bag-light)" stroke-width="2.6" stroke-linecap="round"/>
            <rect class="bag__band" data-bag-band fill="var(--string)" rx="4.5"/>
            <path class="bag__cords" data-bag-cords fill="none" stroke="var(--string)" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>
            <g data-bag-knots fill="var(--string)"><circle r="3.4"/><circle r="3.4"/></g>
          </svg>
        </div>
      </div>
    </div>
    <div class="lamp" aria-hidden="true"></div>
    <p class="hint" data-hint>Click or tap to shuffle the tiles</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.

22 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 wooden tile title” from Animorio and fit it into my site

What it does

A games table covered in green felt, seen from above and a little from the side, with a lamp lighting its middle. At the back, on the left, a red cloth bag with a drawstring and a sewn label drops from above: it hits the felt and squashes as it bounces. The drawstring loosens, the neck widens and you see the dark inside; the bag leans back for a moment to wind up and tips over on its side. Out come the tiles, one after another: little boxes of light wood with grain, thickness and darker edges, and a letter engraved on the face. They fly, hit the felt, bounce three times lower and lower, spinning all the while until they stop crooked, some of them face down; under each one the shadow tightens and darkens as it comes down. The bag, empty now, gets back on its feet, rocking. The tiles that are upside down jump and flip over, the others straighten with a little snap; then, one after another, they slide towards the wooden rack in front, hop over its board and click upright into their place, the rack jolting at each arrival. When the phrase is complete, «The Oak Room», the tiles hop in a row, like a wave. A click (or a tap) shuffles them: the bag jumps and opens wide, the tiles crouch and leap. The ones the next phrase needs too stay, but fly to a new place spinning round and land with a bounce; the ones that are not needed fly into the bag, which swallows them; the missing ones jump out of the bag and reach their place. The rack grows or shrinks with the phrase and at the end the wave says hello again. On a computer, when you pass over a tile it leans towards you. On a narrow screen the phrase wraps onto several racks, one behind the other.

How it works

A tilted world

The stage has a perspective and inside it there is a single div, the world, tilted with rotateX(50deg) and transform-style: preserve-3d: x goes right, y towards us, z up from the felt. Everything on the table lives in there and GSAP moves it with x, y, z and the rotations. The script redoes the browser's own maths (the rotation and the division by the perspective) in two functions: project takes a point of the world to the screen, onFelt does the opposite for the felt. So the rack is put at 83% of the screen height, the bag at 35%, and the tiles land only in the visible part between the two.

A tile is a box

Every tile has three levels: the first puts it in its place and stands it up (rotationX of minus 50 degrees, so its face looks straight at the eye), the second is for hover, the third does the somersaults. Inside are six faces with backface-visibility: hidden, sized by two CSS variables, --s for the side and --t for the thickness. The grain is a canvas painted once by the script (wavy streaks, bands and a knot) and used as a background image, shifted at random for each tile; the colour comes from WOOD. The letter is engraved with two text-shadows: light on the lower lip of the cut, shade on the upper one.

Rolling and bouncing

Every tile comes out of the mouth of the bag, which the script finds by turning that point of the drawing around the corner the bag tips over on. The flight is three tweens together: x and y with power3.out (it rushes and brakes), z with eight keyframes (the first arc, then three bounces lower and lower, power2.in going down and power2.out going up), and the somersaults on rotationX and rotationY, which end on a full turn, or half a turn for the ones left face down. The shadows are blots on the felt that a ticker moves under their tile every frame: the higher the tile, the wider, further and paler the shadow.

The rack and the click

The places on the rack are counted in tile sides: the gap between words, the margin at the ends, and if the phrase does not fit it wraps onto another rack behind. The side is the same for every phrase, the one that makes the longest fit. To arrive, a tile slides on the felt to just behind its place, then with two keyframes hops over the board while standing up and drops into the place with back.out, which gives the click; the rack jolts with a fromTo on z. The rack is made of short pieces side by side, all parallel to the standing tiles: the browser never has to sort crossing planes.

The shuffle

On a click every place of the new phrase looks, among the tiles on the rack, for one with the same letter, the nearest. The tiles found fly to their new place with an arc on z and a full turn on rotationY; the ones left without a place fly into the mouth of the bag, shrink and vanish, and the bag squashes as if swallowing them; for the places without a tile new ones jump out of the bag (old ones are reused). The rack changes length with a tween. A click that comes while the tiles are in the air is kept aside and runs as soon as they land; SPEED is the timeScale of every timeline.

Values to change

NameFileDefaultWhat it changes
PHRASESscript.js['THE OAK ROOM', 'TEA AND TILES', 'GAME NIGHT', 'OPEN LATE']The phrases on the rack, one per click: after the last one it starts again from the first. Letters, numbers and the signs & ! ? ' . , -, up to 28 characters; if they do not fit on one line they wrap onto several racks.
TOSSscript.js1How hard the bag throws the tiles: how high they bounce, how many somersaults they do and how high they jump to turn over.
SHUFFLEscript.js1The shuffle: how high the tiles jump and how many turns they do in the air (2 = two turns and higher jumps).
WOODscript.js'#e2bd86'The colour of the wood of the tiles: the script works out the darker edges and the colour of the engraving from it.
SPEEDscript.js1The speed of the whole animation: 2 is twice as fast, 0.5 half as fast.

Try them in the Playground

When to use it

For the title of a site about games, words or free time: a board game café, a club, a quiz night venue, a language school, a word app, a festival; for a page whose phrase changes on a click, like a greeting, a promise or an anagram.

On phones

The same animation with the sizes adapted: the tiles are smaller and the phrase wraps onto two or three racks, one behind the other; the bag and the rolling tiles fit in the space above. A tap shuffles them.

With reduced motion

Nothing moves: the open bag and the tiles already on the rack fade in. On a click the tiles fade away and those of the next phrase fade in in their place.

Pitfalls

  • The rack was a long box made of 3D faces: Chrome drew only half of it, and the faces sorted badly against each other. Now it is made of short pieces side by side, all parallel to the standing tiles: no crossing planes, no planes that are too long.
  • The tiles on the felt are all at the same height: where they overlap the faces flicker. Each one gets a tiny offset in z (a few tenths of a pixel) and the landing spots are at least a side and a half apart; the letters are given out from left to right, so the paths to the rack hardly cross.
  • Faces sized by the variables --s and --t stay at zero if the tile is born after the sizes are worked out: every new tile gets the variables as soon as it is taken from the pile.
  • Tiles back in the bag left their shadow on the felt: the ticker hides the shadow of every tile that is not in play.
  • The autopilot clicks about every three seconds, even while the tiles are in the air: instead of piling up two shuffles, the click is kept aside and runs as soon as the wave ends.
  • A rotation of 360 degrees left as it is means the next spin, which goes to 360, does not turn at all: at the end of every flight rotationY goes back to 0 with a set.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 15 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