Skip to content
animorio
IT
All components

The pasted stories

Stories, Transitions, ImagesCanvas, GSAPAdvancedCompressed weight: Demo 18 KBGSAP 28 KBFreeDownload the zip

The stories of a concert hall on the harbour, and every story is a poster pasted on the wall: going on, the new poster comes in from the right and is slapped over the old one, full of air bubbles that a hand pushes out from the top down. The wet paper is darker, shines on its bumps and lets the poster underneath show through; the story’s time is the paste drying. Going back, the poster is torn off the wall.

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, viewport-fit=cover">
  <title>The pasted stories</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/sofia-sans@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/sofia-sans-extra-condensed@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the stories of a concert hall, each one a poster pasted on the wall with wheat paste: going on, a new poster
         is slapped over the last one, the hand smooths the air out of it and the wet paper lets the old poster show
         through while the paste dries, which is the story's time; going back, the top poster is torn off. The posters
         are drawn by script.js on the canvas, from STORIES -->
    <section class="stories" aria-label="Stories from Ferry House" aria-roledescription="stories" data-stories data-autoclic>
      <div class="viewer" data-viewer>
        <canvas class="wall" aria-hidden="true" data-wall></canvas>
        <p class="sr" data-now></p>

        <button class="tap tap--back" type="button" aria-label="Previous story" data-back>
          <span class="tap__sign" aria-hidden="true">
            <svg viewBox="0 0 24 24"><path d="M14.5 6.5L9 12l5.5 5.5"/></svg>
          </span>
        </button>
        <button class="tap tap--next" type="button" aria-label="Next story" data-next>
          <span class="tap__sign" aria-hidden="true">
            <svg viewBox="0 0 24 24"><path d="M9.5 6.5L15 12l-5.5 5.5"/></svg>
          </span>
        </button>

        <div class="top" data-chrome>
          <div class="bars" aria-hidden="true" data-bars></div>
          <div class="head">
            <span class="head__mark" aria-hidden="true">
              <svg viewBox="0 0 32 32">
                <circle class="mark__ground" cx="16" cy="16" r="16"/>
                <path class="mark__house" d="M9 15.5l7-6 7 6v4.5H9z"/>
                <path class="mark__door" d="M14 20v-4h4v4"/>
                <path d="M5.5 23.2c1.8 0 1.8-1.4 3.6-1.4s1.8 1.4 3.6 1.4 1.8-1.4 3.6-1.4 1.8 1.4 3.6 1.4 1.8-1.4 3.6-1.4 1.8 1.4 3.6 1.4" class="mark__sea"/>
              </svg>
            </span>
            <span class="head__name">Ferry House</span>
            <span class="head__when">3 h</span>
            <button class="head__pause" type="button" aria-label="Pause the stories" data-pause>
              <svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true"><rect x="6.5" y="5" width="3.6" height="14" rx="1.2"/><rect x="13.9" y="5" width="3.6" height="14" rx="1.2"/></svg>
              <svg class="i-play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.6c0-.9 1-1.5 1.8-1l9.4 5.9c.7.5.7 1.5 0 2l-9.4 5.9c-.8.5-1.8-.1-1.8-1z"/></svg>
            </button>
          </div>
        </div>

        <div class="foot" data-foot>
          <form class="reply" data-reply>
            <label class="sr" for="story-reply">Reply to Ferry House</label>
            <input id="story-reply" type="text" placeholder="Reply to Ferry House" autocomplete="off" enterkeyhint="send" maxlength="140">
          </form>
          <button class="react" type="button" aria-pressed="false" aria-label="Stick a See you there sticker on this story" data-react>
            <svg viewBox="0 0 28 28" aria-hidden="true">
              <path class="react__sheet" d="M7 4.5h10.6l6 6V21a2.5 2.5 0 0 1-2.5 2.5H7A2.5 2.5 0 0 1 4.5 21V7A2.5 2.5 0 0 1 7 4.5z"/>
              <path class="react__fold" d="M17.6 4.5V8a2.5 2.5 0 0 0 2.5 2.5h3.5"/>
              <path class="react__face" d="M10.6 11.4v1.8M15.4 11.4v1.8M10 16.6c1.8 2 4.2 2 6 0"/>
            </svg>
          </button>
        </div>
      </div>
      <p class="sr" aria-live="polite" aria-atomic="true" data-live></p>
    </section>
  </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.

57 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 pasted stories” from Animorio and fit it into my site

What it does

On a white background a story viewer in a phone’s proportions: at the top five thin bars, the hall’s round mark (a little house on the sea), Ferry House, 3 h and the pause button; at the bottom the Reply to Ferry House field and the sticker button. Every story is a poster printed in two or three colours on grainy paper: a sun setting on the sea for MARROW & Kite Weather, a lit doorway in a brick wall for Doors at 8, an Admit one ticket for Last 30 tickets, a guitar’s sound hole with six strings (one vibrating) for Open jam, a pint with its head spilling over for New on tap. At the start last week’s poster is on the wall; the first one comes in from the right, held off the wall and a little crooked, with its shadow on the old one, and as it slows down it lands. As soon as it touches the wall it is wet: darker, with a few air bubbles that swell with a little bounce and shine, the creases of the paper and, under it, blurred, the old poster. At once an invisible hand smooths it from the top down: a wave of paper runs down, pushing the bubbles in front of it, which get squashed and wider, and out at the bottom. Then, while the bar fills, the paste dries in stripes, the brush’s strokes, the thinnest ones and the edges first: the old poster fades, the paper gets lighter and stops shining, and only the faintest creases stay. A tap on the left third goes back: the poster is torn off by its top left corner and goes off turning, with a white torn edge; a thin strip stays stuck along the right edge and flakes away, and under it is the poster before, as it was. Holding, the time stops (the paste stops drying too) and the bars, the head and the reply go away. The sticker button sticks a scalloped See you there sticker on the poster, coming down turning and landing with a bounce; it stays there, under the posters pasted later, and tearing them off finds it again. After the last story the first poster is pasted again over the last.

How it works

Posters drawn on the canvas

Every poster is drawn only once on a canvas as big as the viewer in real pixels (density at most 2), in its own units of 320 by 568: the ground, the drawing with arcs, curves and Path2D, the words centred and widened to 276 units with measureText. Over it, a tile of grain and fibres multiplied and a soft-light shading from the light’s side. The wall is put together every frame from these canvases, without drawing the posters again.

The slap

Going on, the wall as it is gets copied into an under canvas. The new poster comes in from the right held off the wall (scale 1.07, six degrees, its shadow), with x in power2.out for SLAP seconds, while in the second half scale and lift go back to zero with power2.in: it slows down and lands in the same instant. The shadow is blurred only once, with the trick of the shape drawn far away and only its shadow moved onto the canvas, because shadowOffset does not follow the transforms.

The paper’s relief

The paper is a height map on a grid of 80 by 142 cells: long creases (Gaussians along a segment fading at the ends), lumps of paste (smooth noise) and BUBBLES elliptic air bubbles, which swell with elastic.out on impact. The hand’s front goes down in SMOOTH seconds with power1.inOut, a little wavy and crooked; in front of it it pushes the bubbles, which squashed grow wider more than taller, and a wave of paper; behind it, the creases flatten to 38%. Every frame central differences give the slope: its product with LIGHT makes light and shade (a small grey canvas stretched in overlay) and, where it is wet, the direction halfway between the light and the eye makes the shine.

The paste drying

Every cell has its moment to dry: the paste’s thickness, made of stripes like brush strokes plus a noise, times the distance from the edge. The wetness goes from 1 to 0 with a wide smoothstep while the story’s time runs, and the paper is dry before the bar is full. Wet, the paper multiplies its colour by a darker warm grey and lets the poster underneath through: shrunk to a seventh and stretched back (the paper blurs it), cut with destination-in by a mask as high as SHOW_THROUGH times the square of the wetness, and multiplied. With no time running (pause, a press, the focus) the paste does not dry and nothing is drawn again.

The tear

Going back the wall is frozen on a canvas and the poster underneath is worked out again, dry. The tear line runs near the right edge, made of three sine waves and a little noise; from it two Path2D, the piece and what stays. The piece turns up to 24 degrees round its top left corner while it leaves with power2.in in TEAR seconds, with its shadow and the white edge of torn paper; the strip left behind flakes away, dropping a little. At the end the poster revealed is exactly the one worked out for the under canvas: no jump.

Buttons, time and the preview

The tap zones are two real buttons, Previous story and Next story, whose arrow shows for the mouse and the keys; pausing is a button in the head, and the keyboard’s arrows go on and back. A press longer than HOLD seconds stops the time and hides everything but the poster, without changing story. The time also stops when the keys come into the stories or a reply is being written (the button says Play, and pressed it starts again); an aria-live region says the new story, the sticker and the reply. A click from a script (the library’s preview) goes on, sticks a sticker, goes on, tears, and goes on twice.

Values to change

NameFileDefaultWhat it changes
DURATIONscript.js6The seconds each story stays: the paste is dry by then.
SLAPscript.js0.4The seconds a new poster takes to come in and be slapped onto the wall.
SMOOTHscript.js0.62The seconds the hand takes to smooth the poster from the top down.
TEARscript.js0.5The seconds a poster takes to be torn off, going back.
HOLDscript.js0.22The seconds a press must last to stop the time instead of changing story.
BUBBLESscript.js8The air bubbles under a poster just pasted.
SHOW_THROUGHscript.js0.22How much the poster underneath shows through the wet paper, from 0 to 1.
SHINEscript.js0.8How much the wet paper shines on its bumps, from 0 to 1.
LIGHTscript.js[-0.55, -0.83]Where the light comes from, across and down: like this from the top left.
STORIESscript.js[['sun', 'MARROW', '& Kite Weather', 'Friday 20 November, doors at 8', '#e4472b', '#f6ecd8', '#f0b53c'], …]The posters: the drawing (sun, door, ticket, strings, pint), the big words (the bar | starts a new line), the smaller ones, the last line, the ground, the ink and the accent colour.

Try them in the Playground

When to use it

For the stories of a venue, a theatre, a festival or a bookshop that puts up posters: gigs, nights, last tickets, news at the bar. Or as a timed gallery for a poster campaign.

On phones

The viewer is 320 pixels wide and as tall as a phone, and on short screens it shrinks keeping its proportions. It is tapped on the right and the left and held with a finger; the long press does not open the system’s menu nor select text. The canvas is drawn at a density of 2 at most.

With reduced motion

The time starts stopped (the button says Play) and the stories do not go on by themselves. No flight, bubbles, hand or tear: one poster fades to the wall and the other appears, already dry, without their words sitting on each other; the sticker fades in.

Pitfalls

  • The poster underneath, shown through sharp, doubled the titles (the old letters inside the new ones): wet paper blurs it, so it is shrunk to a seventh and stretched back, and its strength goes with the square of the wetness, so it goes before the paper gets lighter.
  • A poster held off the wall but as big as the viewer looks just like one pasted (its shadow is under it): with coming in and landing one after the other, it stood still for a quarter of a second. Now it slows down and lands in the same instant.
  • A Path2D after rect() and arc() joins the rectangle’s corner to the arc’s start with a line: the evenodd cut of the ticket’s notches cut out a wedge. Each arc needs a moveTo before it.
  • A bubble pushed to the bottom by the hand got taller and its shine covered the words under the title: squashed, a bubble grows much wider and only a little taller.
  • The strip left on the wall after a tear covered the poster revealed: it is narrow (ten to thirty units) and flakes away before the piece has gone.
  • shadowBlur and shadowOffset do not follow the canvas’s transforms: the shadow of the poster in the air and of the torn piece is blurred only once on a canvas of its own, drawing the shape far away and moving only the shadow, then drawn with the sheet’s own transform.
  • With reduced movement a crossfade put the words of two posters on each other half way: first one fades to the wall, then the other appears.
  • On an iPhone a long press may not send a click: the flag that makes the click after a press be ignored is reset at every new press, or the next tap would be lost.

Libraries

GSAP 3.15.0, Sofia Sans (font variabile) 5.3.0, Sofia Sans Extra Condensed (font variabile) 5.3.0.

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

Testing

Tested on 6 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 components