Skip to content
animorio
IT
Back to the library

The thumb riffle transition

Transitions, TextGSAP, CSS onlyMediumCompressed weight: Demo 8.6 KBGSAP 28 KBFreeDownload the zip

A minimal page change: on a click the page lifts off the table and turns out to be the top sheet of a pad, the sheets under it just fanned. A thumb riffles it sideways, very fast, and every sheet is a frame: the old headline sinks, the new title rises and gains weight. The pad stops with a small knock on the page marked by the red ribbon.

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 thumb riffle transition</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>
    <!-- the pad: the two real pages are its top and its bottom sheet; the thin sheets in between are added by the script -->
    <div class="deck" data-deck>
      <div class="pan" data-pan>
        <section class="sheet page page--a" data-page="a" aria-label="Notebooks">
          <header class="bar" data-bar>
            <span class="brand">Quire</span>
            <nav class="nav" aria-label="Pages">
              <a href="#notebooks" aria-current="page">Notebooks</a>
              <a href="#archive" data-go="b">Archive</a>
            </nav>
          </header>
          <div class="hero">
            <p class="kicker" data-rise>Notebooks, sewn by hand</p>
            <h1 class="big big--a" data-big>Blank pages.</h1>
            <a class="go" href="#archive" data-go="b" data-autoclic data-rise>
              <span class="go__text">Open the archive</span>
              <span class="go__arrow" aria-hidden="true"><span class="go__shaft"></span><svg class="go__head" viewBox="0 0 8 12"><path d="M1.5 1.5 6.5 6l-5 4.5"/></svg></span>
            </a>
          </div>
          <footer class="foot" data-foot>
            <span>Bound in small runs</span>
            <span>Since 2011</span>
          </footer>
          <span class="sheet__shade" aria-hidden="true"></span>
        </section>

        <section class="sheet page page--b" data-page="b" aria-label="Archive" aria-hidden="true">
          <header class="bar" data-bar>
            <span class="brand">Quire</span>
            <nav class="nav" aria-label="Pages">
              <a href="#notebooks" data-go="a">Notebooks</a>
              <a href="#archive" aria-current="page">Archive</a>
            </nav>
          </header>
          <div class="hero">
            <h2 class="big big--b" data-big>Archive</h2>
            <div class="below">
              <p class="lede" data-rise>Every notebook we have bound since the first one, kept on a shelf and still open to read.</p>
              <a class="go go--back" href="#notebooks" data-go="a" data-autoclic data-rise>
                <span class="go__arrow" aria-hidden="true"><svg class="go__head" viewBox="0 0 8 12"><path d="M6.5 1.5 1.5 6l5 4.5"/></svg><span class="go__shaft"></span></span>
                <span class="go__text">Back to notebooks</span>
              </a>
            </div>
          </div>
          <footer class="foot" data-foot>
            <span>Open on Saturdays</span>
            <span>Since 2011</span>
          </footer>
          <!-- the ribbon marker: it hangs out of the pad at the page the thumb will stop on -->
          <span class="ribbon" data-ribbon aria-hidden="true"></span>
          <span class="sheet__shade" aria-hidden="true"></span>
        </section>
      </div>
    </div>
  </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.

28 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 thumb riffle transition” from Animorio and fit it into my site

What it does

The site of a small made-up bindery, Quire, that sews notebooks by hand, in two pages and almost nothing else: a paper-coloured background, one huge headline in Fraunces, a line of text, a single red accent. On load the pad is set down on the table a little smaller than the screen: the sheets under it fan out by a few pixels and their edge shows its shading, from paper to black; out of the bottom comes the red ribbon marking a page, with a small spring. The headline, Blank pages., rises letter by letter, gaining weight. The thumb tries the corner once: the top sheet lifts a hair and drops back with a knock that shivers through the edges and swings the ribbon; then the pad lies down and fills the screen. A click on Open the archive lifts the page again, the thumb takes the corner and the pad is riffled sideways: the sheets turn around their left edge one after another, in less than a second, and you see the fan of pages in flight. Every sheet is a frame of a flip book: the letters of Blank pages. sink below their line and thin out, then the letters of Archive rise, overshoot by a hair and grow darker, while the paper goes from light to dark. All the while the pad slides so that the top sheet stays where the page was. It stops on the page with the ribbon with a small knock: the pad squashes a hair, the sheets underneath squeeze and spring back, the ribbon swings. Then it lies down, fills the screen and the text and the link back rise in. A click on Back to notebooks riffles it backwards: the sheets fall back onto the pad one on top of the other, the title comes apart, and the first page lands on top with its own knock.

How it works

A pad of real sheets

The two pages are the first and the last sheet of a stack of elements one on top of the other, each as big as the screen, with a falling z-index. The script adds SHEETS fake sheets in between and three sheets under the last page, the rest of the pad. Every sheet has a one-pixel edge made with box-shadow: 0 0 0 1px, which does not change its size. When the pad fills the screen the sheets all sit at zero and only the first one shows.

The frames of the flip book

Sheet k is frame f = k / (SHEETS + 1). Its colour is a mix of paper and black in proportion f, and the type turns from dark to light halfway. The letters of the old headline are pushed down by q² × 1.3 em and lightened, the letters of the new title come up from below with a back.out curve worked out by hand (they overshoot by a hair) and go from 130 to 600 in weight. Every letter has its own delay, so the words come apart and together from left to right. The letters sit in a mask with overflow: hidden, so they vanish below the line.

The thumb

The pad shrinks to LIFT and the sheets move i × SPREAD pixels to the right and a little less down, with half a pixel of untidiness. Then every sheet turns around its left edge to rotateY(90°), edge-on, with a transformPerspective of 1.4 times the width and a shade that darkens as it turns; at 90 degrees it is hidden. The starts are spread over RIFFLE seconds with a curve that slows just a little towards the end. The sheets' container follows the sum of the rotations, so the top sheet stays where the page was.

The knock and the ribbon

When the last sheet has gone, the pad squashes by 1.4% in 0.07 seconds and comes back with elastic.out(1, 0.35); the sheets underneath close in on the top one and spread again with an elastic, and the ribbon is kicked sideways and swings with elastic.out(1, 0.2). The ribbon lives inside the second page, hanging under its edge: with the pad filling the screen it stays off stage, and when the pad lifts it comes out by itself and marks where the thumb will stop. KNOCK sets the strength of the whole knock.

Direction, phone and reduced motion

Each change is one timeline with the labels lift, catch, riffle, knock and close, and timeScale for SPEED. The sheets' starts are worked out as numbers, not as nested labels. A click that comes while the pad is being riffled waits in a queue. Both titles fit the width of the screen and the sheets' offset gets smaller on narrow screens. With reduced motion the sheets in between are never seen and the two pages swap with a fade.

Values to change

NameFileDefaultWhat it changes
SHEETSscript.js12How many sheets sit between the two pages: each is a frame of the flip book. More sheets, a denser riffle and a smoother title.
RIFFLEscript.js0.8The seconds the thumb takes to run through the whole pad.
LIFTscript.js0.84How big the pad stays while it is riffled: 1 = it never leaves the screen.
SPREADscript.js3How many pixels each sheet sticks out from the one above: the fanned edge of the pad. 0 = even sheets.
KNOCKscript.js1How hard the pad knocks when it stops: 0 = it just stops, 2 = a firm knock.
SPEEDscript.js1The speed of everything: 2 is twice as fast, 0.5 half as fast.

Try them in the Playground

When to use it

To move from one page to another on a site about paper and books: a publisher, a magazine, a stationery shop or a bindery, an illustrator's portfolio, a newspaper archive, the step from a list to a book's page. It works well between a light page and a dark one.

On phones

The same riffle with the sizes adapted: the pad becomes a tall narrow notebook, the sheets stick out a little less and the titles shrink to fit the width of the screen. A tap on the link riffles it and riffles it back.

With reduced motion

Nothing moves and nothing turns: the sheets in between are never seen, the first page fades out with the second already under it, title in place, then the text and the link fade in; going back the first page fades back in.

Pitfalls

  • The sheets' starts were written as nested labels (riffle+=0.12 and then +=0.34): GSAP reads only the first offset, and the call that hides a sheet at the end of its turn fired with the start of the turn. Every sheet vanished as soon as it began to turn and the riffle looked like a fade. The positions are now worked out as numbers.
  • Turning towards the viewer, with perspective the sheets grew taller than the screen and left the pad: they turn backwards, the right edge moving away, and stay inside the pad like the pages of an open book.
  • With 0.2 seconds per sheet and an easing that starts slowly, the sheets stood almost still and then vanished: with 0.34 seconds five or six are in flight together and the fan shows.
  • The ribbon had a negative margin of two pixels to tuck it under the sheet, and with the pad filling the screen a red tip stayed at the bottom of the stage: it now starts exactly at the sheet's edge.
  • With reduced motion the first page faded into the first fake sheet, light grey, instead of the second page: the sheets in between have to be hidden from the start.
  • With its inner block set to display: block the title was as wide as the column, and the type fitting could not see the word: the title has width: max-content.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0.

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

Testing

Tested on 14 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