Skip to content
animorio
IT
All components

The tally counter

Counters, SVG, PhysicsGSAPAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

In the minimal style: the people inside a small room, counted twice, with one big thin number and with old-fashioned tally marks on a hairline. With plus the pen draws a mark from the top down, which presses a pixel too far and comes back up; every five the stroke across, the only colour, runs over the four, which rock one after the other and draw together into a bundle. The line ends at a small wall: when the room is full, one plus too many shoves the whole row against the wall, which bends and comes back.

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 tally counter</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the door count of a small room: how many people are in, a minus and a plus, and the same count kept the old
         way, as tally marks on a hairline. Every fifth mark is the stroke across the four; the hairline ends at a
         thin wall, the size of the room. The marks, the line and the wall are drawn by the script -->
    <div class="tally" data-tally>
      <div class="tally__head">
        <p class="tally__label" id="tally-label">Checked in</p>
        <p class="tally__note" id="tally-note" data-note></p>
      </div>

      <div class="tally__row">
        <div class="tally__value" role="spinbutton" tabindex="0" aria-labelledby="tally-label" aria-describedby="tally-note" aria-valuemin="0" aria-valuemax="25" aria-valuenow="7" aria-valuetext="7 people" data-value>
          <span class="tally__num" aria-hidden="true" data-num></span>
        </div>

        <div class="tally__keys">
          <button class="tally__btn" type="button" aria-label="One person out" data-less>
            <span class="tally__btn-in" aria-hidden="true">
              <svg viewBox="0 0 48 48">
                <circle class="tally__ring" cx="24" cy="24" r="23.25"/>
                <path class="tally__icon" d="M17.5 24h13"/>
              </svg>
            </span>
          </button>
          <button class="tally__btn" type="button" aria-label="One person in" data-more data-autoclic>
            <span class="tally__btn-in" aria-hidden="true">
              <svg viewBox="0 0 48 48">
                <circle class="tally__ring" cx="24" cy="24" r="23.25"/>
                <path class="tally__icon" d="M17.5 24h13M24 17.5v13"/>
              </svg>
            </span>
          </button>
        </div>
      </div>

      <svg class="tally__field" aria-hidden="true" data-field>
        <path class="tally__base" d="" data-base/>
        <path class="tally__wall" d="" data-wall/>
        <g data-marks></g>
      </svg>

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

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 tally counter” from Animorio and fit it into my site

What it does

On a white background, at the top left CHECKED IN, small and grey, at the right Room holds 25. Below, on the left, the number of people inside, big and thin, in a sans serif with a variable width; on the right a minus and a plus inside two thin rings. Under them a very thin grey line running from the left up to a small upright wall, the size of the room, and on the line the same people counted as tally marks: four black marks standing and the fifth across them, in blue. With plus the pen draws a new mark from the top down, speeding up; when it reaches the line it presses a pixel too far and comes back up, and the mark before it feels the knock and rocks a little. The fifth is the stroke across: it leaves from the bottom left and climbs to the right, and every mark it crosses rocks on its foot, one after the other; at the end the five draw together into a bundle, and the number gives a beat of weight and goes back to thin. The number changes on the width axis: the old figure squeezes until it disappears, the new one opens up a hair too wide and settles; from 9 to 10 the tens open a new cell and push the units along. With minus the last mark goes back up into the pen; taking away a fifth, the stroke across runs back and the four spread out again. When the room is full the note says Full house and the plus goes dim: pressed again, the whole row is shoved against the wall, a wave runs through it from left to right, the wall bends, turns blue for a moment and stands straight again, and the note turns blue. At zero the minus gives a small no and the note says No one is in yet. With the mouse over a button the ring turns black and the sign gets thicker; pressed, the button gives a little towards the bottom like a soft key. Holding a button the count keeps changing, faster and faster; from the keyboard the number answers the arrows, Page Up and Down (five at a time, a whole bundle), Home and End.

How it works

Marks that are segments

Each mark is an SVG line redrawn every frame, with a foot standing still on the line and a top: the top sits at the mark’s length (a little different from one to the next, with a sine, so they look handwritten but tidy) and at its rocking angle. The stroke is drawn for the fraction p: a new mark goes from p 0 to 1 in DRAW seconds with power2.in, from the top down, like a pen speeding up; minus takes it back to 0 and the mark goes back up. The round cap of a stroke just begun would be a speck, so the mark fades in over the first 14% of the stroke.

The stroke across

The fifth mark has no place of its own: its two ends are worked out from the feet of the first and the fourth of the bundle, six pixels further out, from 20% to 80% of the height. While it is drawn (power3.inOut, SLASH seconds) every frame looks at where its tip is: when it passes a mark’s foot, that mark gets a push on its angle, so the four rock one after the other to the beat of the stroke. Going back with minus the tip passes the marks the other way and gives a smaller push the other way.

The springs

Each mark has three damped springs: the angle on its foot (SPRING, 320 and 0.3), the press from the top (900 and 0.34: landing, the mark gets a speed that shortens it by about one pixel and comes back) and the sideways shift that draws a closed bundle together by 16% around its middle. The loop takes steps of 1/240 of a second at most, a slow frame counts as 1/30, and it takes itself off GSAP’s ticker when everything is still.

The figure that squeezes

The number is set in Roboto Flex, whose width runs from 25 to 151. Each figure sits in a cell as wide as a zero, measured at the start and at every change of size; changing, the old figure goes to wdth 25 and fades, the new one starts at 25 and reaches 100 with back.out, which takes it a hair past and settles it. The units change first, the tens 50 thousandths later. With two figures the tens cell is born zero wide and opens, pushing the units along; when the count changes faster than every 0.24 seconds (holding a button) the swap takes less than half the time, so there is always a figure to read. The beat of weight when a bundle closes is a CSS variable, --wg, which GSAP takes from WEIGHT[0] to WEIGHT[1] and back with an elastic.

The wall and the preview

At the most, plus changes nothing: a shove spring moves the row towards the wall, more on the left and not at all next to the wall, so the bundles close up; each mark gets its push after a delay of its distance divided by 900 pixels a second, and when the wave arrives the wall, a quadratic curve, bends and takes the colour for half a second. A button counts on the press (pointerdown) and repeats while held; Enter and Space count in the click. The scripted click of the library’s preview plays a whole tour in a GSAP timeline: up to a full room, one plus too many, and back down to where it was.

Values to change

NameFileDefaultWhat it changes
MAXscript.js25How many people the room holds: the highest number and the marks that fit up to the wall (5 to 40).
STARTscript.js7How many people are inside when the page opens.
DRAWscript.js0.16How long the pen takes to draw one mark, in seconds.
SLASHscript.js0.26How long the stroke across a five takes, in seconds.
ROCKscript.js1How much the marks rock when the stroke crosses them or the row hits the wall (0 = not at all).
SPRINGscript.js[320, 0.3]The marks’ spring: how stiff it is and how much it is held back (1 = no wobble).
WEIGHTscript.js[280, 560]The number’s weight at rest and on the beat when a bundle of five closes (100 to 1000).
HOLDscript.js[0.42, 0.18, 0.07]Holding a button: how long before it starts repeating, the first gap and the shortest, in seconds.
WORDscript.js['person', 'people']The word for one and for more than one, read out to people using a screen reader.
NOTEscript.js['Room holds {max}', 'Full house', 'No one is in yet']The note at the top right: usually, when the room is full and pressing minus at zero; {max} becomes the highest number.

Try them in the Playground

When to use it

For counting by hand something that grows one at a time and has a ceiling: the people coming through the door of an event or a workshop, the covers of an evening, the places on a guided tour, the reps of an exercise in an app. The marks tell at a glance how far it is to the wall, even without reading the number.

On phones

The number shrinks with the screen (a quarter of its width at most) and the bundles spread over the width there is, up to the wall: at 390 pixels five bundles sit in a row without overflowing, and with many bundles they get a little smaller. A finger works like the mouse: a tap on a button counts one and makes it give, held down it repeats faster and faster. A long press opens no menu and two quick taps do not zoom the page.

With reduced motion

No pen, rocking, shoves or bending walls: a new mark fades in already whole, one taken away fades out, the bundle is already drawn together, the figures change with a fade without squeezing and the note changes with a fade. Buttons and keys work as always.

Pitfalls

  • In the preview’s tour the count goes down one every 0.12 seconds, but the new figure waited 0.1 seconds and faded in over 0.16: it was replaced before it could be seen and the units vanished, leaving only «2» instead of «21». When changes come faster than every 0.24 seconds the swap takes 45% of the time, and there is always a figure.
  • A mark just begun, or nearly gone, was a dot: the round cap of a stroke of zero length. The mark now fades in over the first 14% of its stroke.
  • The stroke across cannot have a fixed position: when the bundle draws together the marks move and the stroke would have stayed wider than them. Its ends are worked out every frame from the feet of the first and the fourth mark.
  • Pressing plus right after minus, the mark going back up and the new one were drawn on top of each other. The mark on its way out, if it is needed again, starts again from where it is.
  • The note going out and the one coming in overlapped for a moment in their 18 pixel slot: the old one now leaves in 0.2 seconds and the new one starts 0.14 seconds later.
  • A mouse click comes after pointerdown, which has already counted: it counted twice. The click that follows a press is ignored, but Enter and Space count in the click; the preview is told apart because its click is not isTrusted.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile) 5.3.0.

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

Testing

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