Skip to content
animorio
IT
All components

The marble jar

Counters, Physics, SVGGSAP, CanvasAdvancedCompressed weight: Demo 19 KBGSAP 28 KBFreeDownload the zip

A kudos counter: a glass jar of cat’s eye marbles with a paper tag tied round its neck with string, carrying the number. Give kudos drops a marble in from above the rim: it falls in, bounces off the glass and the others, rolls with its vanes turning and settles, and as it lands the number on the tag rolls up. Held, it pours one marble after another until the jar is full; minus plucks the top marble and lifts it out. The jar can be dragged sideways, and the marbles knock against the glass.

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 marble jar</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the marble jar: a kudos counter, a glass jar of cat's eye marbles with a paper tag tied round its neck. Give
         kudos drops a marble in from above: it falls into the jar, bounces off the glass and the other marbles, rolls
         and settles, and the number on the tag rolls on. Held, the button pours a stream until the jar is full; the
         minus button lifts the top marble out. The jar can be dragged sideways to rattle them. The glass is drawn by
         the script in two layers, behind and in front of the marbles, which are a canvas -->
    <div class="kj" data-kj>
      <div class="kj__scene" role="button" tabindex="0"
           aria-label="The kudos jar. Drag it sideways, or press Enter, to rattle the marbles" data-scene>
        <div class="kj__move" data-move>
          <svg class="kj__layer" viewBox="-170 -380 340 396" aria-hidden="true" data-art="back"></svg>
        </div>
        <canvas class="kj__layer kj__marbles" aria-hidden="true" data-canvas></canvas>
        <div class="kj__move" data-move>
          <span class="kj__edge" data-edge></span>
          <span class="kj__edge" data-edge></span>
          <svg class="kj__layer" viewBox="-170 -380 340 396" aria-hidden="true" data-art="front"></svg>
          <div class="kj__tag" aria-hidden="true" data-tag>
            <svg class="kj__string" viewBox="0 0 64 26" aria-hidden="true">
              <path class="kj__string-back" d="M32 0C30.5 8 29.4 16 30 25.5"/>
              <path class="kj__string-front" d="M32 0C33.6 8 34.8 16 34 25.5"/>
            </svg>
            <div class="kj__card">
              <span class="kj__hole"></span>
              <span class="kj__kicker">Kudos</span>
              <span class="kj__count" data-count></span>
              <span class="kj__for" data-for>for Marta</span>
            </div>
          </div>
        </div>
      </div>

      <div class="kj__actions">
        <button class="kj__btn kj__btn--less" type="button" aria-label="Take a kudos back" data-less>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.5 12h11"/></svg>
        </button>
        <button class="kj__btn kj__btn--more" type="button" aria-describedby="kj-hint" data-more data-autoclic>
          <span class="kj__word" data-word>Give kudos</span>
          <svg class="kj__plus" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 6.5v11M6.5 12h11"/></svg>
        </button>
      </div>
      <p class="sr" id="kj-hint">Hold to pour.</p>
      <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.

47 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 marble jar” from Animorio and fit it into my site

What it does

On a white background a clear glass jar, faintly green, with a rolled rim and a thick bottom, standing on its own soft shadow; inside it twelve cat’s eye marbles, clear glass with coloured vanes inside (tomato, cobalt, jade, marigold, violet, rose), a highlight at the top left, a darker edge and a coloured light under each. Round the neck two turns of string with a knot, and hanging from the knot a kraft paper tag with a reinforced hole: KUDOS, the big number and for Marta. Below, a round minus and a dark Give kudos + pill, 58 pixels tall. A click on Give kudos brings a marble in from above: it drops into the mouth of the jar, passing behind the rim, knocks into the marbles already there, bounces, rolls with its vanes turning while the highlight stays put, and settles; the moment it touches, the number on the tag rolls up, the old figure leaving at the top and the new one coming in from below. Holding the button (or Enter) the marbles come one every 120 milliseconds until the jar is full, and then the button goes dim and says Jar’s full. Minus takes the topmost marble: a little pluck, then it rises straight out of the mouth and fades, and the others settle again. The jar can be taken with the mouse or a finger and dragged sideways: the glass slides, the walls push the marbles, which knock about and mix, the tag swings on its string, and let go the jar comes back to its place with a little bounce; a tap without dragging is a knock on the glass. From the keyboard the jar is shaken with Enter or Space and with the arrows.

How it works

Circles with gravity

Each marble is a circle with a position, a speed, an angle and a spin, in the drawing’s units (a little more than a pixel). The world moves on GSAP’s ticker in fixed steps of 1/480 of a second (a slow frame counts as 1/30 at most): gravity (GRAVITY) adds speed, then the contacts are found between the marbles and with the inside of the jar, a box with rounded bottom corners, open at the top.

Knocks as impulses

Each contact is solved with an impulse along the line between the centres: it can never pull, it takes away the speed with which two marbles close in and gives back BOUNCE of it (BOUNCE_GLASS against the glass), but only in a knock, when the contact is new and the marbles close in faster than 45 units a second: a contact that was already there a step ago is a resting one and never bounces, so the marbles come to rest instead of jittering. Across the contact there is friction, capped at FRICTION times the push: it acts on the contact point and turns the marbles, so they roll instead of sliding. The walls and the bottom move with the jar, and their speed enters the sums: that is how the marbles feel the jar being shaken.

A pile that stands still

The pushes of every contact are remembered from one step to the next and used again as a first guess (warm starting): a pile of forty marbles stands without trembling. After the pushes, whatever still overlaps is pulled apart by moving the marbles, without touching their speeds. Once every marble has been nearly still for a third of a second the world goes to sleep: nothing is drawn again until a marble arrives, one is taken out or the jar is shaken.

Marbles painted once

Each kind of marble is three pictures painted once for the screen’s density: the tinted glass, the vanes (pointed, twisted blades, deep in the middle and bright at the tips, with a few bubbles) and on top the dark edge, the light gathered low on the right and the highlight at the top left. Every frame they are laid down with drawImage, and only the vanes turn with the marble. Before them, on the bottom, each one’s shadow and a light of its colour.

The glass in front and behind

The jar is SVG in two layers: behind the canvas the shadow on the page, the back of the glass, the far half of the rim and the floor; in front the thick bottom, the sides seen edge-on, the reflections, the near half of the rim and the string, its twist drawn as short slanted strokes. A falling marble goes through the mouth in front of the far rim and behind the near one. Dragged, the jar follows the hand on a spring, and the tag is a pendulum that feels its acceleration.

The number when the marble lands

The real count changes at once on the click (the button, the limit, the words read out), but the tag counts the marbles that have arrived: at a new marble’s first touch the figure that changes rolls, the old one leaving at the top and the new one coming in with back.out. How many marbles the jar holds is worked out from their size, so not even in the Playground does the pile ever go over the rim.

Values to change

NameFileDefaultWhat it changes
NAMEscript.js'Marta'Who the kudos are for: it is on the tag and in the words read out.
STARTscript.js12The marbles already in the jar when the page opens, settled in their places.
MAXscript.js40How many marbles the jar holds; with bigger marbles it holds fewer, and it never overflows.
HOLDscript.js[0.32, 0.12]Holding the button: how long before it starts to pour, and how often a marble comes, in seconds.
GRAVITYscript.js2600Gravity, in the drawing’s units per second squared: higher, the marbles fall and settle faster.
BOUNCEscript.js0.42How much a marble bounces off another (0 = not at all, 1 = fully).
BOUNCE_GLASSscript.js0.33How much a marble bounces off the glass of the jar.
FRICTIONscript.js0.3The friction of glass on glass: it is what makes the marbles roll instead of sliding.
ROLLINGscript.js1.4How quickly a rolling marble loses its spin, per second.
SIZEscript.js[16, 18.4]The marbles’ radius, the smallest and the largest, in the drawing’s units (10 to 21).
SHAKEscript.js32How far the jar can be dragged to either side.

Try them in the Playground

When to use it

For counting something that piles up and is nice to watch grow: kudos or thanks in a team, stars given to a project, points in a collection, tips, the days in a row of a habit, the votes of a small ranking. It suits a person’s profile card, an internal board, an app for teams or for school, anywhere a rising number should feel like a reward rather than a statistic.

On phones

At 390 pixels the jar takes the whole width and the buttons stay 58 tall; nothing overflows sideways and the canvas uses the screen’s density, so the marbles stay sharp. A finger works like the mouse: a tap gives a marble, held down it pours, the jar can be dragged sideways with a finger (the page still scrolls up and down) and a tap on the glass is a knock. A long press opens no menu and two quick taps do not zoom the page.

With reduced motion

No falls, bounces, rolling or shaking: the new marble appears already settled where it would have landed (the world runs all its steps at once, without drawing them) and fades in; the one taken out fades where it was and the others are already in place. The number changes with a fade instead of rolling, the tag does not swing and the jar cannot be dragged.

Pitfalls

  • The full pile never went to sleep: the marbles at the bottom had speeds between 50 and 110 units a second while staying put, and it was drawn again forever. The pushes reused from the step before (warm starting) were applied while the contacts were still being made, and the contacts made afterwards measured their bounce on speeds those pushes had already changed: still marbles deep in the pile bounced. Now all the contacts are made and the bounces decided first, then the pushes are applied: the full jar stops half a second after the last marble.
  • Shaking the jar hard threw marbles over the rim: inside, the walls keep them in line even higher up (they go up forever), but above the jar they seemed to float out. A marble’s upward speed has a ceiling, just what it needs to reach a little below the rim.
  • At forty marbles the pile went over the rim: a heap of circles thrown in at random fills about seven tenths of the space, not the nine of tidy ones. The jar is taller, the marbles a little smaller, and the most it holds is worked out from the marbles’ size, so big marbles do not overflow in the Playground.
  • In the Playground, with bounce and gravity both at their highest, the pile never stopped: deep in a tall pile the solver leaves closing speeds that grow with gravity, and above the threshold they became bounces that gave energy back. Now only a new contact bounces; one that was already there a step ago is a resting one.
  • With no friction and no loss of spin, a spinning marble spun for ever (its spin was tied to nothing) and the world never went to sleep: the spin always loses a little, to the air, and the speed under which a marble counts as still grows with gravity.
  • With marbles bigger than the rounded corners of the bottom, the corner’s radius came out negative and marbles left the glass: the corner has a smallest radius and the marbles a largest radius of 21.
  • A marble appearing at the top of the canvas, on the white, came out of nowhere: the top of the canvas fades out with a mask, and the marbles come into it from outside.
  • The tag rolled on the click, while the marble was still in the air: now it counts the marbles that have arrived, at their first touch. The full button, minus and the words read out follow the real count instead, at once.
  • A click after a press counted two marbles (pointerdown pours, then click comes): the press marks the click as counted and the mark is cleared on the next frame; the click of the automatic preview and of screen readers, which comes with no press, gives one marble.
  • The jar’s focus ring did not show: an inset shadow is drawn under the children, and the layers of glass covered it. It is a pseudo-element on top of everything.
  • The two halves of the rim, with different gradients, met at the sides in two white wedges: where they touch they have the same glass, and the ends are two half rounds.

Libraries

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.

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

Testing

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