Skip to content
animorio
IT
All components

The crumbling button

Buttons, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

Delete project comes apart into thousands of grains: from left to right the button crumbles and a wind full of eddies carries the red dust away, while in its place the dark note Project deleted with Undo gathers out of the wind. Undo runs it all backwards, and the grains fly home into the button.

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 crumbling button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the crumbling button: pressed, the button is painted on a canvas, its pixels become grains and a wind full of
         eddies carries them off from left to right; in its place "Project deleted" gathers out of the same wind, and
         Undo plays the whole film backwards until the button is whole again -->
    <!-- the button and the note that takes its place share one cell; their shadows stand apart, so that they can fade
         while the grains fly -->
    <div class="action" tabindex="-1" data-action data-autoclic>
      <div class="action__item" data-del-wrap>
        <span class="action__shadow action__shadow--del" aria-hidden="true" data-del-shadow></span>
        <button class="del" type="button" data-del>
          <svg class="del__icon" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M4 6.5h16M9.5 6.5V5a1.5 1.5 0 0 1 1.5-1.5h2A1.5 1.5 0 0 1 14.5 5v1.5" data-lid/>
            <path d="M6 6.5l.9 12.6A2 2 0 0 0 8.9 21h6.2a2 2 0 0 0 2-1.9L18 6.5M10 10.5v6.5M14 10.5v6.5"/>
          </svg>
          <span class="del__label">Delete project</span>
        </button>
      </div>

      <div class="action__item" data-toast-wrap>
        <span class="action__shadow action__shadow--toast" aria-hidden="true" data-toast-shadow></span>
        <div class="toast is-gone" role="group" aria-label="Project deleted" data-toast>
          <svg class="toast__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.5a8.5 8.5 0 1 0 0-17a8.5 8.5 0 0 0 0 17z"/><path d="M8.6 12.2l2.3 2.3 4.6-4.7"/></svg>
          <span class="toast__text">Project deleted</span>
          <span class="toast__rule" aria-hidden="true"></span>
          <button class="undo" type="button" data-undo>
            <svg class="undo__icon" viewBox="0 0 24 24" aria-hidden="true"><g data-undo-turn><path d="M8.5 14.5L4 10l4.5-4.5"/><path d="M4 10h10.5a5 5 0 0 1 0 10H11"/></g></svg>
            <span class="undo__label">Undo</span>
          </button>
        </div>
      </div>
      <p class="sr" aria-live="polite" data-live></p>
    </div>
  </main>
  <!-- the grains in the wind, over the whole page -->
  <canvas class="dust" aria-hidden="true" data-dust></canvas>

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

20 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 crumbling button” from Animorio and fit it into my site

What it does

On a white background a red Delete project button with a bin drawing; with the mouse over it the bin's lid lifts and the button brightens a little. Pressing it, the button crumbles from left to right along a ragged front: its pixels become grains, still at first, then caught by a wind blowing to the right and a little upwards that turns them in eddies, and they thin out until they vanish. As the button leaves, other grains arrive in its place from the left, dark ones, that slow down and land: a black note gathers with a green tick, Project deleted and an Undo button. Pressing Undo runs the film backwards: the note comes apart into the wind from right to left, the red grains come back out of the wind and settle in their places, and the button is whole again.

How it works

The button painted

On the click the button (and the note that will take its place) is painted again on a canvas at the screen's resolution, from the computed styles: the gradient, the two inner lines of light, the SVG icon stroked with Path2D and the label on the page's own baseline (an empty element put into the line for a moment tells where it is). The painting starts from the same whole pixel as the page, so swapping the real button for its painting does not show.

The grains

The painting's pixels are read with getImageData in square cells of GRAIN pixels (about 1,700 grains for the button, 3,000 for the note). Each grain has its home, the average colour of its cell, a moment it sets off (its x along the button times SWEEP, plus a noise that makes the ragged front) and a flight time. They are drawn grouped by colour, one fillRect each, on a fixed canvas over the page.

The wind with its eddies

The wind is WIND to the right, lifted by LIFT, plus the eddies: the curl of a noise field at two scales, worked out once on a grid every 8 pixels. The velocity is the noise's slope turned a quarter of a turn, so the air swirls without ever piling the grains up or tearing them apart; the eddies are carried by the wind at a little over half its speed, and CURL sets their strength.

The film worked out once

Every grain's path is integrated ahead with the midpoint method, 32 steps, and kept in a Float32Array; it is worked out as soon as the page is still, and again after a resize, so the click only has to paint. The whole film is a function of a single time that GSAP moves. The note gathers by integrating backwards from its place against the same breeze: played forwards, each grain arrives with the wind and slows as it lands.

Undo, backwards

Undo brings the time back to zero in REWIND seconds with power2.inOut: the note comes apart into the wind and the button's grains fly home the same way they left. An image of what is whole is updated cell by cell, cleared when a grain leaves and painted back when it returns; the shadows under the button and the note follow the same time, with a soft mask that moves with the front.

Values to change

NameFileDefaultWhat it changes
WINDscript.js300The speed of the wind, in pixels a second.
CURLscript.js130How hard the eddies in the wind turn the grains, in pixels a second.
LIFTscript.js0.16How much the wind lifts the grains, as a share of its speed.
SWEEPscript.js0.75The seconds the crumbling takes to cross the button from left to right.
FLIGHTscript.js1.3The seconds a grain flies before it is gone.
GRAINscript.js1.5The size of a grain, in pixels.
REWINDscript.js1.15The seconds Undo takes to put the button back together.

Try them in the Playground

When to use it

For the action that deletes something and can be undone: a project, a file, a conversation, an item in a list. The button coming apart says the thing is gone, and the film running backwards says that Undo truly brings it back. Best on a rare action: it is a show, not a gesture to repeat a hundred times a day.

On phones

The button and the note sit in the middle of the page, in the same place; the grains are painted at the screen's density (up to 2) and the cells grow on their own if there would be too many. The wind carries the grains past the edge of the screen without making it scroll sideways. A tap works like a click, and the bin's hover is not there.

With reduced motion

No grains and no wind: the button fades out and the note fades in in its place, and Undo does the same the other way round. The bin does not open its lid, the press shows in the light alone and the button does not come up on arrival.

Pitfalls

  • The painting must match the real button to the pixel: getBoundingClientRect gives positions with decimals, while the browser draws the box on whole pixels. The painting starts from the corner rounded the same way, and the label goes on the true baseline, read from an empty element in the line: worked out from the font's measures it sat one pixel too high.
  • Working out the paths of 5,000 grains on the click took up to a third of a second before anything showed. The paths depend only on where the pieces are, not on how they look: they are worked out ahead, with the page still, and the click only paints (with the hover of that moment).
  • Any noise used as a wind piles the grains into clumps and leaves holes. The curl of a noise field is a flow without sources: the grains swirl in eddies while staying spread like dust.
  • The gathering note, with its grains all taken by the full wind, arrived as a black smear crossing the page. It comes from a weaker breeze (a third of the wind, the same eddies) and its grains only turn opaque close to their place.
  • The button's shadow is outside the canvas: left alone it stayed as a pink rectangle where the button no longer was. It follows the film's time, with a soft mask that moves with the front; a clip-path made a hard cut.

Libraries

GSAP 3.15.0, Geist (font variabile) 5.3.0.

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

Testing

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