Skip to content
animorio
IT
All components

The abacus stepper

Counters, Physics, SVGGSAPAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

How many guests at a table, counted on one row of an abacus: ten lacquered beads on a brass rod, and the ones pushed to the right are the guests. Plus and minus give one end of the rod a little knock: it runs through the beads like a Newton’s cradle, the last one shoots off, slides, knocks into the others and bounces back a hair, and the number rolls on its reels as it lands. The beads can also be dragged and thrown.

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 abacus stepper</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/opsz.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- how many guests at a table, as one row of an abacus: lacquered beads on a brass rod in a black lacquer frame,
         and the beads pushed to the right are the guests. Plus and minus fire a little hammer at one end of the rod:
         the knock runs through the beads like a Newton's cradle and the last one shoots across. The beads can also be
         dragged and thrown. The number's reels, the beads and the rod are made by the script -->
    <div class="guests" data-guests>
      <div class="guests__head" aria-hidden="true">
        <span class="guests__count" data-count></span>
        <span class="guests__note" data-note></span>
      </div>

      <div class="abacus" data-abacus>
        <button class="abacus__btn" type="button" aria-label="Remove a guest" data-less>
          <span class="abacus__disc" aria-hidden="true">
            <svg viewBox="0 0 24 24"><path d="M6.5 12h11"/></svg>
          </span>
        </button>
        <div class="abacus__track" role="spinbutton" tabindex="0" aria-label="Guests" aria-valuemin="1" aria-valuemax="10" aria-valuenow="2" aria-valuetext="2 guests" data-track>
          <div class="abacus__groove" data-groove>
            <span class="abacus__layer" data-shades></span>
            <svg class="abacus__rod" aria-hidden="true" data-rod></svg>
            <span class="abacus__layer" data-beads></span>
          </div>
        </div>
        <button class="abacus__btn" type="button" aria-label="Add a guest" data-more data-autoclic>
          <span class="abacus__disc" aria-hidden="true">
            <svg viewBox="0 0 24 24"><path d="M6.5 12h11M12 6.5v11"/></svg>
          </span>
        </button>
      </div>

      <p class="sr" aria-live="polite" data-said></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.

34 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 abacus stepper” from Animorio and fit it into my site

What it does

On a white background a row: on the left the number 2 in a serif typeface and below it, in grey, guests; on the right a black lacquer frame 64 pixels tall with a minus and a plus on two dark discs and, between them, a groove with a brass rod and ten lacquered beads, five vermilion and five ivory, counted from the right. The beads pushed to the right are the guests: when the page opens there is one, then a little knock sends a second one over. With plus the rubber washer on the left knocks the beads on the left: the knock runs through them like a Newton’s cradle and the last one leaves to the right, slows down as it slides along the rod and knocks into the others; the knock runs through them too, the last one squeezes the washer at the far end, and the bead that arrived bounces back a few pixels before settling in its place. Right at the knock the number rolls on its reels, with a little jolt past the figure. Minus does the same from the other side. The rod flexes a hair at every knock. With the mouse over a button its hammer cocks: the washer draws back a little and the beads follow it. A bead can be taken with a finger or the mouse, pushed into the others and thrown: the count updates when everything stops. A tap on a bead sends it to the other side together with those between it and the gap. At 10 the plus goes dim and maximum rises under the number in red; pressing it again the frame gives a dull knock and the beads are pressed against their stop; the same with minus at 1, with minimum. Holding a button the beads leave one after the other, faster and faster; from the keyboard the rod answers the arrows, Home and End.

How it works

Physics along a line

Each bead has a position and a speed along the rod, in pixels. The sums run on GSAP’s ticker in steps of 1/600 of a second at most (and a slow frame counts as 1/30): a viscous friction (FRICTION) takes speed away, a constant pull of 650 pixels per second squared brings each bead back towards its pile, as if the rod sloped down a little towards both ends, and the washers are damped springs (RUBBER) with brass behind them, which stops a bead once the rubber is fully squashed. When nothing moves the loop stops and draws nothing.

Knocks, pair by pair

Two beads that touch and close in on each other knock: the impulse between them takes away their closing speed and gives back BOUNCE of it, half to each (below 24 pixels a second they no longer bounce, so they come to rest). The pairs are solved one at a time, going over the row again until none closes in: that is how a knock walks along a row of touching beads and comes out of the last one, which is the Newton’s cradle. A bead held in a hand has infinite mass: it pushes the others and does not give way.

The right knock

Plus knocks the first bead of the left pile, minus the last bead of the right one. Along a row of n beads the knock keeps (1 + BOUNCE) / 2 of itself at every pair, so the bead that leaves gets V · ((1 + BOUNCE) / 2)^(n − 1): the script picks V so that it reaches the other pile at ARRIVE pixels a second, adding FRICTION times the distance to travel, because viscous friction takes away speed in proportion to the way covered.

Which side a bead is on

Every bead has two places, in the left pile and in the right one, and belongs to the side whose place is nearer: the border is half way between the two. The beads cannot pass each other, so this always gives a run on the left and a run on the right. With the buttons and the keys the side is decided by the command; with hands it is read from the positions, and the number only changes once everything has stopped.

The number’s reels

Tens and units are two strips of eleven figures in a window that fades out at the top and the bottom. One number moves them, going from the old count to the new one: the units turn with it, the tens only while the units go from 9 to 0, like an odometer. The number arrives with back.out, and its strength is picked for each jump so that it goes past by a fifth of a figure, however far it travels.

The limit and the rod

At a limit the hammer knocks dully: the frame is kicked to the left and comes back on a fully damped spring; the beads inside feel its acceleration the other way round and are thrown against the stop on the right. The rod is a damped oscillator that every knock pushes a little, a pixel at most: the beads follow it along a parabola, more in the middle than at the ends.

Values to change

NameFileDefaultWhat it changes
MINscript.js1The fewest guests.
MAXscript.js10The most guests, which is also the number of beads on the rod (3 to 12).
STARTscript.js2The guests when the page opens.
ARRIVEscript.js380How fast a bead reaches the other pile, in pixels a second: how hard it knocks.
BOUNCEscript.js0.9How much of a knock passes from one bead to the next (1 = all of it, like a Newton’s cradle).
FRICTIONscript.js3.2How quickly a bead sliding along the rod slows down.
RUBBERscript.js[9000, 0.36]The rubber washers at the ends of the rod: how stiff they are and how much they hold back (1 = no bounce).
FLEXscript.js1How much the rod flexes when the beads knock (0 = a rigid rod).
HOLDscript.js[0.42, 0.17, 0.07]Holding a button: how long before it starts repeating, the first gap and the shortest, in seconds.
WORDscript.js['guest', 'guests']The word under the number, for one and for more than one.

Try them in the Playground

When to use it

For choosing how many people or things when the number is small and it matters: guests at a table, seats in a booking, nights, portions, tickets, items in a cart. It suits places that want a calm, physical gesture, like a real object, such as booking a restaurant or a wine bar, and where five beads of one colour help to read the number at a glance.

On phones

Below 520 pixels the number moves above the rod and the frame takes the whole width, so the beads stay large and have room to run; at 390 pixels nothing overflows. A finger works like the mouse: a tap on a button counts one, held down it repeats faster and faster, a bead can be dragged and thrown (the page still scrolls up and down) and a tap on a bead sends it to the other side. A long press opens no menu and two quick taps do not zoom the page.

With reduced motion

No flights, bounces, frame knocks, rod flexing or cocking hammers: the bead that changes side fades out where it was and appears in its place in the other pile, and the number changes with a fade instead of rolling. The beads can still be dragged; let go, they reach their places with a fade.

Pitfalls

  • The rod’s blurred shadow did not show: a straight horizontal path has a box zero pixels tall, and the filter, which by default is measured on that box, had an empty area. The filter is measured in page pixels (filterUnits userSpaceOnUse).
  • With two independent reels, going from 2 to 10 the number showed 16 and 17 for a moment: now both reels follow a single number and the tens only turn during the units’ last step.
  • back.out goes past in proportion to the way travelled: a jump from 2 to 10 flashed an 11. Its strength is worked out for each jump from the formula of how far it goes past (4s³ / 27(s + 1)²) so that it is always a fifth of a figure.
  • At a limit the frame got a speed and the beads did not: in the frame’s reference they must get the opposite speed at the same instant, otherwise the pile lifted off its stop instead of being thrown against it.
  • When a block of beads lands the knocks add up in a single step and the rod bent by several pixels, into a V: the speed the knocks give the rod has a ceiling, so it never goes past a pixel.
  • With friction alone a bead that bounced on arrival stopped half way, on neither side: it needs the pull towards its own pile. Too weak, the bounce was long and the return limp; at 650 pixels per second squared, with more damping in the rubber, the bead goes back 8 pixels and is in place within half a second.
  • With a low BOUNCE the knock does not get through the row: to make the last bead leave it would take a huge knock, which drags the whole pile along. The knock is capped at four times the leaving speed and in the Playground BOUNCE goes from 0.7 to 1.
  • Running the mouse quickly over a button left its hammer cocked and a pile of beads two pixels out of place: the tween that cocks (0.35 s) lasted longer than the one that uncocks (0.3 s) and, without overwrite, ran to its end. With overwrite: 'auto' the last one rules; the hammer’s kick, another property of the same object, is left alone.
  • Playwright, and with it the test run, will not click a button with aria-disabled: the data-autoclic mark moves to the other button as soon as a limit is reached, so the preview goes back and forth.

Libraries

GSAP 3.15.0, Newsreader (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 15 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