Skip to content
animorio
IT
All components

The skipping stone button

Buttons, Physics, HoverCanvas, GSAPAdvancedCompressed weight: Demo 18 KBGSAP 28 KBFreeDownload the zip

The Skip intro button of a video player, made as a little ceramic dish of clear water over sand, with the sun's net of light on the bottom. Pressed, a flat stone flies in from the left and skips: five or six hops, each shorter than the one before, each with its own rings spreading and setting the light dancing on the sand; then it sinks, stays on the bottom and the words turn to Intro skipped.

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 skipping stone button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/figtree@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the skipping stone button: the Skip intro button of a video player, made as a little ceramic dish of clear,
         sunlit water over pale sand. Pressed, a flat stone flies in from the left and skips across the water, every
         hop shorter than the one before, then sinks to the bottom and stays there. The water is drawn on the first
         canvas, the stone and the splashes on the second; the words stay on top -->
    <div class="skip" data-skip>
      <button class="skip__dish" type="button" data-dish data-autoclic>
        <canvas class="skip__water" aria-hidden="true" data-water></canvas>
        <canvas class="skip__air" aria-hidden="true" data-air></canvas>
        <span class="skip__label" data-label>
          <span class="skip__words" data-words>Skip intro</span>
          <span class="skip__icons" aria-hidden="true" data-icons>
            <svg class="skip__icon" viewBox="0 0 24 24" data-skip-icon>
              <path class="skip__tri" d="M6.4 6.6v10.8c0 .9 1 1.4 1.7.9l7.7-5.4c.6-.4.6-1.4 0-1.8L8.1 5.7c-.7-.5-1.7 0-1.7.9z"/>
              <path d="M18.8 5.8v12.4"/>
            </svg>
            <svg class="skip__icon skip__icon--done" viewBox="0 0 24 24" data-check>
              <path d="M5 12.6l4.4 4.3L19.2 7.2" data-tick/>
            </svg>
          </span>
        </span>
      </button>
      <p class="sr" aria-live="polite" data-live></p>
    </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.

26 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 skipping stone button” from Animorio and fit it into my site

What it does

On a white background a pill-shaped dish of glazed ceramic, as tall as a button and seen from above at an angle: the ivory rim, thicker at the front, the inside wall showing at the far side above the water and then, darker, under it. Inside there is clear aquamarine water over pale sand with the ripple marks the waves leave, a few pebbles towards the ends and two flat stones already lying on the bottom on the right; on the sand a soft net of sunlight. In the middle, dark, Skip intro and the skip forward icon. When the page opens a drop falls into the water and spreads two rings. With the mouse over it the icon's triangle leans towards its bar and the pointer is a fingertip trailed through the water: behind it opens a wake of ripples that bend the sand and the light. Pressing, the dish shrinks a little and a ring starts where it is pressed. At a click a grey flat stone comes flying in from the left over the page, with its shadow, crosses the rim and touches the water: a spray of droplets, a ring of foam and a circle spreading into an oval as it looks from an angle, with the light on the sand bending into rings. The stone spins and leaps again, five or six times, each hop lower and shorter than the one before, drifting a little towards the far side of the dish, and the rings of the hops cross and bounce off the walls; now and then a wave catches the sun and glints. Once wet, the stone turns darker and shinier. At last, too slow to bounce, it ploughs a little furrow, goes under and sinks rocking like a leaf down to the sand, tinted by the water, and raises a puff of sand: it stays there on the bottom, where the light keeps playing over it. Meanwhile the words turn to Intro skipped with a tick that draws itself, and after a second and a half back to Skip intro. Every throw is a little different, and the last five stones stay on the bottom: the oldest are covered with sand. When the water is still again nothing is drawn. With the Tab key a petrol green ring goes round the dish, and Enter or Space throw the stone.

How it works

The stone, a body in flight

The stone has a place in the dish's space (across, away and in height above the water), a velocity, a spin and a pitch held by a spring. It starts over the page, on the left, with the upward push worked out so that it first touches the water just inside the rim. At every touch the water takes away the same amount of energy, LOSS times that of the throw (THROW), and throws it up again in proportion to the speed it has left (LIFT): so every hop is shorter than the one before by about the same length, as in the simple model physicists use for stone skipping. When too little speed is left it ploughs the water, stops and sinks.

The water, the wave equation

The water is a grid of heights, one cell for each pixel, moved by the wave equation: every cell is pulled towards its neighbours, and those towards the back count half, because at 45 degrees distances away from us look shortened by sin 45; so the rings come out oval by themselves. Against the wall a cell uses itself as a neighbour, and the wave comes back. At every step the wave loses a little of its height (CALM) and a touch of viscosity dims the finest ripples first; the steps a second grow with the speed of the waves (WAVE), so the sums stay stable. A touch is a bell-shaped dent, as deep as the blow.

A little ray tracer

Every pixel of the water is worked out as a ray: from the pixel comes the point of the surface (on screen 4 pixels lower than the rim, which is why the dry wall shows at the far side), the slope there bends the eye's ray (BEND) and sends it onto the sand a little farther on, or onto the wall under the water. The sand's colour, lit, goes through the water: --water says how much of each colour gets through, --glow how much light the water adds by itself. On top, a veil of sky that grows on the slopes turned away (Fresnel) and the glints where the surface sends the sun to the eye. The picture is made at two pixels for each css pixel, with the slopes interpolated.

The caustics

The net of light on the sand is real: every cell of the surface sends its ray of sunlight down to the bottom, moved by its slope, and lays it on the sand between the four nearest cells; where many rays land together the sand is bright, where few land it is darker. At rest there is always a very faint long swell, five fixed sine waves, enough to lay a soft net; the stone's rings set it dancing in rings. The walls cast a shadow where the ray of sunlight, bent in the water, would meet them before reaching the rim: it is worked out once.

The bottom and the sunken stones

The sand is drawn once on a canvas: the ripple marks, the pebbles with their shadows, the grain. The sinking stone is drawn on the canvas above, tinted by the water, at the depth where the sand is seen; when it reaches the bottom it becomes part of the picture of the sand, so the light and the waves play over it as over the rest. STONES of them stay: the oldest fade as if covered with sand.

When everything is still

After every frame the highest wave is checked: when for a few frames it is under a few thousandths of a pixel and nothing is flying, the heights go back to zero, the picture at rest is drawn and the loop draws nothing more. Along the edge of the water every pixel mixes water and wall by how much of it each covers, and a pale thread makes the meniscus. On a slow device, if a frame costs more than 9 milliseconds, the water drops to one pixel for each css pixel.

Values to change

NameFileDefaultWhat it changes
THROWscript.js340How hard the stone is thrown, in pixels a second: harder skips more times.
LIFTscript.js0.46How much the water throws the stone up at every touch: more, higher and longer hops.
LOSSscript.js0.13The share of the throw's energy every touch takes away: less, more hops.
SPINscript.js30How fast the stone spins on itself, in radians a second.
RIPPLEscript.js1How strong the rings are where the stone touches the water.
WAVEscript.js150How fast the rings spread, in pixels a second.
CALMscript.js1How quickly the water settles again: more is sooner.
CAUSTICSscript.js1How strong the net of sunlight on the sand is (0, no net).
BENDscript.js1How much the water bends the sand seen under it.
WAKEscript.js1How much the fingertip, the pointer, stirs the water going over it (0, not at all).
STONESscript.js5How many stones stay on the bottom before the oldest is covered with sand.
DONEscript.jsIntro skippedThe words once the stone has sunk.
BACKscript.js1.6The seconds before the words go back.

Try them in the Playground

When to use it

For the button that skips something in a player or a flow: Skip intro, Skip recap, Skip ad in a video or podcast player, Skip for now on the welcome screens of an app. It belongs where summer, water, travel and nature belong: documentary platforms, travel and holiday apps, sites for beach clubs, pools and campsites. It is a button to show off, one to a page, not one for a toolbar.

On phones

The dish is 60 pixels tall like the other buttons and 320 wide, or the screen minus 48; the water is worked out again at the real size. A tap throws the stone and starts a ring where the finger touches; a finger dragged across leaves its wake in the water (touch-action: pan-y, so up and down the page still scrolls). The stone comes in from the left over the page's margin, which does not scroll sideways. On a slow phone the water is drawn at one pixel for each css pixel.

With reduced motion

No flight and no waves: at a click the new stone already lies on the bottom and the old picture of the water fades away over it; the words and the icon change in a fade, with no slide and no blur. The pointer does not stir the water, no drop falls when the page opens, and the dish pressed darkens a little instead of shrinking.

Pitfalls

  • The damping multiplied onto the height at every step took from the wave only the square root of that factor (the product of the scheme's two roots): the water never came to rest and the page drew sixty frames a second even when still. Now the damping is set on the root and the water rests in a couple of seconds.
  • Strong damping and viscosity together dimmed the fine ripples, the very ones that draw the lines of the caustics, and the stone's rings no longer showed: the viscosity is down to a touch, the damping halved and the dents deeper.
  • The water drawn at one pixel for each css pixel left a staircase along the curved edge of the water at the ends: now it is drawn at two pixels for each css pixel with the slopes interpolated, and along the edge every pixel mixes water and wall by how much of it each covers.
  • A water colour that took away too much hid the sand and the dish looked like a swimming pool: the tint is split in two, what the water lets through (--water, light) and what it adds by itself (--glow, dark), and the sand shows.
  • The stone sank straight down where it touched and, looking at 45 degrees, the bottom shows lower than the surface: the stones ended half hidden under the front rim. Now the throw drifts a little towards the far side of the dish, as real throws do, and the sinking stone is drawn at the depth where the sand is seen, so it does not jump when it becomes part of the bottom.
  • The wall under the water painted in a colour of its own made a hard dark stripe at the far side of the dish: now it takes the sky's light like the sand and meets it without a seam.

Libraries

GSAP 3.15.0, Figtree (font variabile) 5.3.0.

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

Testing

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