Skip to content
animorio
IT
Back to the library

Tossed cards

Hover, PhysicsGSAPEasyCompressed weight: Demo 5.2 KBGSAP + Inertia 31 KBFreeDownload the zip

A board of postcards. Brush past one with the pointer and its picture is thrown the way the pointer was going, as fast as it was going, twists a little and comes back to its place. The faster the brush, the further it flies; on a phone a tap throws it in a random direction.

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>Tossed cards</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/instrument-serif@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/instrument-serif@5.3.0/400-italic.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>
  <header class="bar">
    <a class="brand" href="#">Wish You Were</a>
    <nav class="bar__nav" aria-label="Sections">
      <a href="#">Issues</a>
      <a href="#">Shop</a>
      <a href="#">About</a>
    </nav>
  </header>

  <main class="board">
    <div class="intro">
      <h1 class="intro__title">Postcards from places <em>that don’t exist.</em></h1>
      <p class="intro__hint" data-hint>Brush past a card: it flies off and comes back.</p>
    </div>

    <!-- each card: the figure keeps the place on the board, the inner part is what gets thrown -->
    <div class="cards">
      <figure class="card" data-card style="--r: -4deg; --y: 10px">
        <div class="card__art" data-toss>
          <svg viewBox="0 0 300 400" aria-hidden="true">
            <rect width="300" height="400" fill="#1d2b4a"/>
            <circle cx="205" cy="96" r="34" fill="#f3e7c9"/>
            <path d="M0 250 70 170 120 215 175 140 240 220 300 180V280H0Z" fill="#121b30"/>
            <rect y="276" width="300" height="124" fill="#1a3150"/>
            <path d="M160 300h90M175 320h60M190 340h30" stroke="#f3e7c9" stroke-width="5" stroke-linecap="round" opacity="0.5"/>
          </svg>
          <figcaption>Greetings from <span>Lago Nero</span></figcaption>
        </div>
      </figure>
      <figure class="card" data-card style="--r: 3deg; --y: -6px">
        <div class="card__art" data-toss>
          <svg viewBox="0 0 300 400" aria-hidden="true">
            <rect width="300" height="400" fill="#ffd9a0"/>
            <circle cx="150" cy="215" r="74" fill="#ff6b35"/>
            <rect y="232" width="300" height="168" fill="#2a6f97"/>
            <path d="M30 262h90M160 262h110M60 292h70M170 292h80M20 322h120M180 322h60M70 352h150" stroke="#7fb6d5" stroke-width="6" stroke-linecap="round"/>
          </svg>
          <figcaption>Greetings from <span>Saltmarsh</span></figcaption>
        </div>
      </figure>
      <figure class="card" data-card style="--r: -2deg; --y: 18px">
        <div class="card__art" data-toss>
          <svg viewBox="0 0 300 400" aria-hidden="true">
            <rect width="300" height="400" fill="#ffc2b4"/>
            <circle cx="88" cy="108" r="30" fill="#fff1d6"/>
            <path d="M0 250C70 190 130 200 180 240S270 260 300 230V400H0Z" fill="#f2937a"/>
            <path d="M0 310C60 270 120 280 170 300S260 330 300 300V400H0Z" fill="#e86f51"/>
            <path d="M0 360C80 330 150 350 210 370S280 380 300 360V400H0Z" fill="#c74b37"/>
          </svg>
          <figcaption>Greetings from <span>Dune Nine</span></figcaption>
        </div>
      </figure>
      <figure class="card" data-card style="--r: 5deg; --y: 0px">
        <div class="card__art" data-toss>
          <svg viewBox="0 0 300 400" aria-hidden="true">
            <rect width="300" height="400" fill="#dfe9d1"/>
            <path d="M40 330V190a50 50 0 0 1 100 0v140M160 330V190a50 50 0 0 1 100 0v140" fill="none" stroke="#2f6b4f" stroke-width="10"/>
            <path d="M90 330V230M210 330V230" stroke="#2f6b4f" stroke-width="6"/>
            <path d="M60 330c10-40 40-50 50-80 10 30 0 60-10 80zM180 330c0-50 30-60 40-90 14 40 4 70-10 90z" fill="#3f8a5f"/>
            <rect y="330" width="300" height="70" fill="#b9cfa4"/>
          </svg>
          <figcaption>Greetings from <span>Glasshouse</span></figcaption>
        </div>
      </figure>
      <figure class="card" data-card style="--r: -5deg; --y: -8px">
        <div class="card__art" data-toss>
          <svg viewBox="0 0 300 400" aria-hidden="true">
            <rect width="300" height="400" fill="#2a1f4d"/>
            <path d="M60 70l4 10 10 4-10 4-4 10-4-10-10-4 10-4zM230 120l3 7 7 3-7 3-3 7-3-7-7-3 7-3zM170 55l2 5 5 2-5 2-2 5-2-5-5-2 5-2z" fill="#ffcf5c"/>
            <rect x="20" y="210" width="260" height="80" rx="16" fill="#4b3a86"/>
            <rect x="40" y="228" width="40" height="28" rx="5" fill="#ffcf5c"/>
            <rect x="100" y="228" width="40" height="28" rx="5" fill="#ffcf5c"/>
            <rect x="160" y="228" width="40" height="28" rx="5" fill="#ffcf5c"/>
            <rect x="220" y="228" width="40" height="28" rx="5" fill="#ffcf5c"/>
            <path d="M0 300h300M0 318h300" stroke="#8a78c9" stroke-width="5"/>
          </svg>
          <figcaption>Greetings from <span>Night Train</span></figcaption>
        </div>
      </figure>
      <figure class="card" data-card style="--r: 2deg; --y: 14px">
        <div class="card__art" data-toss>
          <svg viewBox="0 0 300 400" aria-hidden="true">
            <rect width="300" height="400" fill="#2f7fd1"/>
            <path d="M20 330q30-16 60 0t60 0 60 0 60 0 40 0M20 360q30-16 60 0t60 0 60 0 60 0 40 0" fill="none" stroke="#bfe0ff" stroke-width="6" stroke-linecap="round"/>
            <ellipse cx="110" cy="170" rx="58" ry="44" fill="#ffd23f" transform="rotate(-18 110 170)"/>
            <ellipse cx="200" cy="230" rx="50" ry="38" fill="#ffd23f" transform="rotate(14 200 230)"/>
            <path d="M150 120c20-30 50-30 70-20-20 20-45 26-70 20z" fill="#3a8c4a"/>
            <path d="M236 190c16-24 40-26 56-18-16 18-36 22-56 18z" fill="#3a8c4a"/>
          </svg>
          <figcaption>Greetings from <span>Lemon Coast</span></figcaption>
        </div>
      </figure>
      <figure class="card" data-card style="--r: -3deg; --y: 4px">
        <div class="card__art" data-toss>
          <svg viewBox="0 0 300 400" aria-hidden="true">
            <rect width="300" height="400" fill="#cfe6f5"/>
            <path d="M0 260 90 120 150 200 210 110 300 250V400H0Z" fill="#ffffff"/>
            <path d="M90 120 120 260H0ZM210 110 240 250H300Z" fill="#a9c8e0" opacity="0.7"/>
            <path d="M120 300l40-34 40 34v40h-80z" fill="#d9483b"/>
            <rect x="148" y="312" width="22" height="28" fill="#7a2620"/>
            <rect y="340" width="300" height="60" fill="#ffffff"/>
          </svg>
          <figcaption>Greetings from <span>Snow Hut</span></figcaption>
        </div>
      </figure>
    </div>
  </main>

  <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/InertiaPlugin.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.

40 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 “Tossed cards” from Animorio and fit it into my site

What it does

The board of an invented postcard magazine, Wish You Were: the headline “Postcards from places that don’t exist.”, with its second half in red italics, and seven postcards of invented places, each with its illustration and a “Greetings from…” line, slightly crooked and slightly out of line. On arrival they are dealt onto the board one after the other, turning and bouncing. Passing the pointer over a postcard, the picture sets off in the pointer’s direction at its speed, slows down and comes back exactly to its place, while it twists a few degrees and straightens up. Every postcard has a weight of its own: the heavier ones fly further and twist more. Passing over a postcard still in flight, the new throw starts from where it is.

How it works

The place and the picture

Every postcard is a figure, turned and moved slightly with two CSS variables: that is its place on the board and it does not move. Inside is the picture with its border and caption, and that is what flies, so the place stays put even halfway through a throw.

The pointer’s speed

The script keeps the pointer’s last movement between one event and the next. When the pointer enters a postcard, that movement times PUSH, kept within MAX pixels per second, becomes the speed of the throw, multiplied again by the postcard’s weight (WEIGHTS).

The throw with InertiaPlugin

The throw is a single tween: inertia with a speed for x and one for y and an end of 0. The plugin works out the duration and the curve by itself: the postcard leaves at the given speed, slows down and lands exactly where it was. RESISTANCE says how quickly it brakes, and the timeline runs PACE times faster.

The twist

At the same moment a second tween starts on the rotation: up to half of WOBBLE degrees at random, in TWIST seconds, and back with yoyo. Both have overwrite auto, so a new throw takes over the one under way without a jolt.

Which postcard

Entering each postcard is not listened to: a single pointermove listener on the window looks at which postcard the pointer is in and throws when it changes. So a fast pass over three postcards throws all three, once each.

Values to change

NameFileDefaultWhat it changes
PUSHscript.js14How much of the pointer’s last movement goes into the throw: pixels per second for each pixel.
MAXscript.js900The fastest throw, in pixels per second.
WOBBLEscript.js30The widest twist, in degrees, half each way.
TWISTscript.js0.4The seconds the twist takes out, and the same back.
PACEscript.js1.2How much faster than its own timing the throw plays.
RESISTANCEscript.js100How quickly a thrown postcard brakes: higher, shorter flights.
TAPscript.js650On a phone, how hard a tap throws a postcard, in pixels per second.
WEIGHTSscript.js1, 0.8, 1.25, 0.9, 1.15, 1, 0.85Each postcard’s weight, in order: heavier flies further and twists more.
--cardstyle.cssclamp(140px, 14vw, 214px)A postcard’s width.

Try them in the Playground

When to use it

For any grid of cards, covers, products or photos that should feel alive under the mouse: a portfolio, a shop, a gallery. It works best with well-spaced cards and a solid background; on cards packed together the thrown ones cover their neighbours.

On phones

The postcards go into two columns and the page scrolls. A tap throws the postcard in a random direction, nearly always upwards, at 650 pixels per second; scrolling with a finger throws nothing.

With reduced motion

The postcards are already in place on arrival and do not fly: neither the pointer nor a finger moves them.

Pitfalls

  • Postcards turned near the edges, and those in flight, widen the page: on a phone it could be scrolled sideways by 9 pixels. html and body have overflow-x: clip, which cuts without creating a scrolling box.
  • With a pointerenter listener on every postcard, an event created by a script with bubbles: true, like those of an autopilot, would bubble up from the children and start a throw for every shape crossed. Instead, at every move, the script looks at which postcard the pointer is in.
  • The throw with an end of 0 starts from where the postcard is: that is why a second pass during the flight does not teleport it, it throws it again.

Libraries

GSAP 3.15.0, InertiaPlugin (GSAP) 3.15.0, Instrument Serif 5.3.0, DM Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 5.2 KB; the libraries: GSAP + Inertia 31 KB.

Testing

Tested on 2 October 2026

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

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