Skip to content
animorio
IT
Back to the library

The paper plane

Scroll, SVGGSAPAdvancedCompressed weight: Demo 17 KBGSAP + ScrollTrigger 45 KBLenis 5.3 KBFreeDownload the zip

Scroll inside the preview

The page of an invented postal service: at the top a sheet of airmail paper folds itself, in 3D, into a paper plane. As you scroll the plane flies down the page on a curved route, banks in the turns, brushes past the text blocks that step aside on a spring, loops the loop and leaves a dashed red and blue trail; at the bottom it glides into a mailbox that shuts and raises its flag.

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>L'aeroplanino di carta</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header class="barra">
    <a class="marchio" href="#" aria-label="Paperwing Post, home">
      <svg class="marchio__segno" viewBox="0 0 40 40" aria-hidden="true">
        <rect class="marchio__bordo" x="3" y="3" width="34" height="34" rx="3" />
        <path class="marchio__ala" d="M9 21L31 11L22 30L19 23Z" />
        <path class="marchio__piega" d="M19 23L31 11" />
      </svg>
      <span>Paperwing Post</span>
    </a>
    <nav class="voci" aria-label="Main">
      <a class="voce" href="#rotta">Rates</a>
      <a class="voce" href="#giro">Express</a>
      <a class="voce" href="#arrivo">Track</a>
    </nav>
  </header>

  <main>
    <!-- the opening: the sheet of paper folds itself into a plane -->
    <section class="apertura" data-apertura>
      <div class="apertura__testo">
        <p class="occhiello" data-entra>Airmail without the airport</p>
        <h1 class="titolo">
          <span class="riga"><span data-riga>Letters that</span></span>
          <span class="riga"><span data-riga><em>fly themselves.</em></span></span>
        </h1>
        <p class="sotto" data-entra>Write it, fold it, let it go. Paperwing Post handles the wind, the weather and the last ten metres to the door.</p>
        <div class="azioni" data-entra>
          <a class="pillola" href="#arrivo"><span class="pillola__fondo"></span><span class="pillola__testo">Send a letter</span></a>
          <p class="cenno" data-cenno><span>Scroll to launch</span>
            <svg viewBox="0 0 24 32" aria-hidden="true"><path d="M12 4V27M5 20L12 27L19 20" /></svg>
          </p>
        </div>
      </div>
      <div class="apertura__foglio" data-posto-foglio aria-hidden="true"></div>
    </section>

    <!-- the route: the plane brushes past these blocks and they step aside -->
    <section class="rotta" id="rotta" data-rotta>
      <article class="blocco blocco--sx" data-blocco>
        <div class="blocco__corpo" data-corpo>
          <svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
            <circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
            <text x="40" y="44">FOLDED</text>
          </svg>
          <h2 data-entra-blocco>Folded by hand</h2>
          <p data-entra-blocco>Every letter leaves the counter as a paper plane: two creases, a sharp nose and a last look against the light.</p>
        </div>
      </article>
      <article class="blocco blocco--dx" data-blocco>
        <div class="blocco__corpo" data-corpo>
          <svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
            <circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
            <text x="40" y="44">ROUTED</text>
          </svg>
          <h2 data-entra-blocco>Routed by the wind</h2>
          <p data-entra-blocco>Our planes read the breeze, not the map. Expect a few curves on the way: they still arrive before the bus.</p>
        </div>
      </article>
      <article class="blocco blocco--sx" data-blocco>
        <div class="blocco__corpo" data-corpo>
          <svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
            <circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
            <text x="40" y="44">WAXED</text>
          </svg>
          <h2 data-entra-blocco>Weatherproof wings</h2>
          <p data-entra-blocco>Waxed paper, a rounded nose and a tail that forgives. Rain slows us down, but only a little.</p>
        </div>
      </article>
      <article class="blocco blocco--dx" data-blocco>
        <div class="blocco__corpo" data-corpo>
          <svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
            <circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
            <text x="40" y="44">TRACKED</text>
          </svg>
          <h2 data-entra-blocco>Tracked to the door</h2>
          <p data-entra-blocco>Look up. If it is folded, striped and flying low over your street, it is probably yours.</p>
        </div>
      </article>
    </section>

    <!-- the express service: the plane loops the loop here -->
    <section class="giro" id="giro" data-giro>
      <div class="giro__testo" data-blocco>
        <div class="blocco__corpo" data-corpo>
          <p class="occhiello" data-entra-blocco>Express</p>
          <h2 data-entra-blocco>One loop the loop, <em>on the house.</em></h2>
          <p data-entra-blocco>Express letters take the scenic route for a second. Nobody has ever asked for a refund.</p>
        </div>
      </div>
      <div class="giro__spazio" data-giro-spazio aria-hidden="true"></div>
    </section>

    <!-- the landing: a glide into the mailbox, the door shuts and the flag goes up -->
    <section class="arrivo" id="arrivo" data-arrivo>
      <div class="arrivo__testo" data-blocco>
        <div class="blocco__corpo" data-corpo>
          <p class="occhiello" data-entra-blocco>Last ten metres</p>
          <h2 data-entra-blocco>Straight into <em>the box.</em></h2>
          <p data-entra-blocco>No knocking, no missed deliveries. The plane glides in, the door shuts and the flag goes up.</p>
          <svg class="bollo" data-bollo viewBox="0 0 150 80" aria-hidden="true">
            <rect x="4" y="8" width="142" height="64" rx="8" />
            <rect x="11" y="15" width="128" height="50" rx="5" />
            <text x="75" y="48">DELIVERED</text>
          </svg>
        </div>
      </div>
      <div class="arrivo__cassetta" data-posto-cassetta aria-hidden="true"></div>
    </section>

    <footer class="piede">
      <p><strong>Paperwing Post</strong> Letters by air, folded by hand.</p>
    </footer>
  </main>

  <!-- the sky over the whole page: the trail, the plane and the mailbox are drawn here by the script -->
  <svg class="cielo" data-cielo aria-hidden="true">
    <defs>
      <pattern id="ap-righe" width="22" height="22" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
        <rect width="22" height="22" class="righe__carta" />
        <rect width="6" height="22" class="righe__rosso" />
        <rect x="11" width="6" height="22" class="righe__blu" />
      </pattern>
      <!-- the letter printed on the front of the sheet, in sheet units: 200 wide, 300 tall -->
      <g id="ap-lettera">
        <path class="lettera__bordo" fill-rule="evenodd" d="M-100 0H100V300H-100ZM-91 9V291H91V9Z" />
        <rect class="lettera__etichetta" x="-80" y="24" width="62" height="20" rx="2" />
        <text class="lettera__via" x="-49" y="38">BY AIR MAIL</text>
        <g class="lettera__francobollo">
          <rect x="44" y="20" width="38" height="46" />
          <rect class="francobollo__dentro" x="49" y="25" width="28" height="36" />
          <path class="francobollo__ala" d="M53 49L73 39L66 56L63 50Z" />
        </g>
        <text class="lettera__saluto" data-saluto x="-76" y="132">Dear Ada,</text>
        <path class="lettera__righe" d="M-76 156c22-4 44 3 66-1s40 2 62-1M-76 176c26-3 50 3 76 0s34 2 52-1M-76 196c20-3 40 2 60 0s46 3 70-1M-76 216c30-3 58 2 86-1M-76 236c22-2 46 3 70-1s30 2 40 0" />
        <text class="lettera__firma" x="70" y="272">June</text>
      </g>
      <!-- the back of the sheet: only the striped border -->
      <g id="ap-retro"><path class="lettera__bordo" fill-rule="evenodd" d="M-100 0H100V300H-100ZM-91 9V291H91V9Z" /></g>
      <clipPath id="ap-dentro" data-clip-dentro><rect data-clip-fuori x="0" y="0" width="0" height="0" /><path data-clip-bocca d="" /></clipPath>
      <mask id="ap-maschera" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100" data-maschera>
        <path data-scia-maschera class="scia__maschera" d="" />
      </mask>
    </defs>
    <g mask="url(#ap-maschera)" data-scia>
      <path class="scia scia--rosso" data-scia-rosso d="" />
      <path class="scia scia--blu" data-scia-blu d="" />
    </g>
    <g data-cassetta-dietro></g>
    <g data-aereo-fuori><g data-aereo></g></g>
    <g data-cassetta-davanti></g>
  </svg>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.

52 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 paper plane” from Animorio and fit it into my site

What it does

The page belongs to «Paperwing Post», an invented postal service that delivers letters by flight. At the top, next to the title «Letters that fly themselves.», a sheet of airmail paper appears, with its red and blue striped border, a «By air mail» label, a stamp and a handwritten letter. The sheet folds itself: the two top corners come down to the centre, the new edges fold to the centre again, the sheet closes in half while turning sideways and the wings open with a little bounce; the plane hops and hangs in the air, bobbing gently. Every crease is real 3D: the faces turn around their creases and get darker or lighter as they catch the light, and the back of the sheet is white with the same stripes. As you scroll, the plane takes off and flies down the page on a curved route, staying at about the same height on the screen: it always points where it is going, banks in the turns and rolls over when it turns back, so it stays upright. It brushes past four text blocks, which step aside on a spring and tilt a little while their stamps spin in. In the «Express» section it loops a full loop, upside down at the top. Behind it a dashed trail, one red dash and one blue like the edge of the envelope, always ends under its tail. At the bottom it glides towards a mailbox with its door open, lifts its nose a little, gets smaller and goes into the dark mouth; the door shuts on a spring, the mailbox jolts, three bang lines fly out, the red flag goes up and the «Delivered» stamp lands beside it. Scroll back and everything rewinds and the door opens again. The pointer on a block spins its stamp, on the mailbox it waves the flag; the links at the top make a wave letter by letter and the button swells with a bounce.

How it works

The sheet cut into faces

The sheet is 200 units wide and 300 tall, with the nose at the top. The script folds half a sheet on paper: the corners to the centre, the new edges to the centre, and at every fold it cuts the pieces along the crease line and flips the ones that move, remembering the way back to the flat sheet and the list of creases that move them. Then the wing crease cuts everything one last time. Out come sixteen faces (eight per side), each drawn in the coordinates of the flat sheet.

Creases as 3D rotations

Every crease is a rotation around a line of the flat sheet (Rodrigues’ formula): corners, second corners, halves and wings. The position of a face is the product of the rotations of its chain, from the outermost to the innermost. The plane is then turned towards the screen: from the upright sheet facing you to the flight orientation with a spherical interpolation (quaternions), and in flight with the heading, the roll and a little yaw.

SVG faces with the printed letter

Seen without perspective a flat face stays a parallelogram, so a transform matrix(a b c d e f) on an SVG group with the polygon in sheet coordinates is enough. Inside is the letter (a use cut by the face’s clipPath) when the front shows, just the striped border when the back shows, and a dark polygon whose opacity depends on how the face catches the light. The drawing order: far panels first, then on each panel the stack of paper from the hidden side to the visible one.

The route and the scroll

The route is a smooth curve (Catmull-Rom turned into Béziers) running beside every block, measured from the page, then a loop the loop and the glide into the mailbox. The script samples it every 3 pixels and for each point works out the scroll that brings it to 60% of the screen; in the loop the scroll moves evenly, so on the screen the loop is round. A single tween with ScrollTrigger and scrub carries the scroll value, and a binary search finds the point.

Trail, blocks and mailbox

The trail is two dashed paths (red and blue, offset) seen through a mask with a single dash as long as the whole route: the stroke-dashoffset uncovers it up to the tail. Every frame the blocks measure their distance from the plane and move with gsap.quickTo and elastic.out. On arrival a timeline shuts the door (scaleY from -0.52 to 1 with elastic.out), jolts the mailbox, raises the flag with back.out and drops the stamp; scrolling back plays it in reverse.

Values to change

NameFileDefaultWhat it changes
FOLD_SPEEDscript.js1How quickly the sheet folds itself: 0.5 slow, 2 quick.
PLANE_SIZEscript.js1.2The size of the plane in flight.
LOOP_SIZEscript.js1The size of the loop the loop.
DODGEscript.js56How many pixels the blocks step aside when the plane brushes past.
BANKscript.js0.6How much the plane leans into the turns: 0 keeps it level.
DASHscript.js9The length of every dash of the trail, in pixels.
SCRUBscript.js0.9How many seconds the plane takes to catch up with the scroll: 0 follows it at once.
--rossostyle.css#d2412fThe airmail red: the stripes of the sheet, the trail, the flag and the stamps.

Try them in the Playground

When to use it

For a page that walks the reader from one point to another: a shipping or delivery service, a newsletter, an invitation, a contact form that «takes off», a messaging app, a stationery shop, a «how it works» page that ends with the delivery.

On phones

The same animation with adapted sizes: the sheet folds under the title, the blocks are wider and the plane, smaller, flies in the free strip beside them; the loop is smaller and the mailbox sits above the closing text, so the trail never crosses the words. The blocks step aside a little less.

With reduced motion

No folding, no flight and no smooth scrolling: the plane is already folded at the top and fades in, the trail is already drawn all the way to the mailbox, the mailbox is shut with its flag up and the «Delivered» stamp is already in place. The texts only fade in and the blocks stay still.

Pitfalls

  • Sorting the faces by the depth of their centre alone got the folded flaps wrong: while the sheet closed in half they vanished behind the face they lay on. Now the panels (halves and wings) are sorted first and then, on each panel, the stack of paper according to the side it is seen from.
  • With creases at exactly 180° the stacked faces lie on the same plane and flicker. The angles stop at 177° and 173°: a thickness too thin to see but enough to keep them apart.
  • The loop’s room must be set before measuring the page: without it the loop ended up below the mailbox, the scroll at the bottom got squashed and the plane flew off the bottom of the screen. The script gives the section the height of the loop plus the scroll it needs, and only then measures.
  • A Playwright evaluate returning gsap.globalTimeline.timeScale(k) hangs: the value returned is the whole timeline to copy. In the tests the slow motion goes in an addInitScript.
  • The dashed trail cannot be uncovered with its own stroke-dashoffset, which the dashes already use: the mask is uncovered instead, an identical path with a single dash as long as the whole thing.

Libraries

GSAP 3.15.0, Lenis 1.3.26, Fraunces (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP + ScrollTrigger 45 KB, Lenis 5.3 KB.

Testing

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