Skip to content
animorio
IT
All components

The string bag cart

Carts, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 19 KBGSAP 28 KBFreeDownload the zip

A greengrocer's cart where the order is a cotton string bag hanging from a brass rail: every piece of fruit in the cart is a body in a small physics world. With plus a piece is tossed in on a real arc, knocks the others and rolls into a gap while the net gives under the weight and the bag bobs on its handles; with minus the top one is plucked out and the rest fall into its place. The total is on a kraft tag tied to the knot at the bottom, which spins right round when delivery becomes free.

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 string bag cart</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <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">
    <!-- a greengrocer's cart, and the order is a string bag hanging from a brass rail: every piece of fruit in the cart
         is tossed into the net, falls, rolls and settles among the others, and the net stretches round them; one less
         is plucked out and the rest drop into the gap. The total is on a kraft tag tied to the knot at the bottom, and
         the tag spins round on its string when delivery becomes free -->
    <section class="cart" aria-labelledby="cart-title" data-cart data-autoclic>
      <header class="cart__head">
        <h2 class="cart__title" id="cart-title">Your market bag</h2>
        <p class="cart__count" data-count></p>
      </header>

      <div class="stall" data-stall>
        <div class="stall__art" data-art>
          <canvas class="stall__net" aria-hidden="true" data-net></canvas>
          <div class="tag" aria-hidden="true" data-tag>
            <div class="tag__card" data-tag-card>
              <span class="tag__total" data-tag-total></span>
              <span class="tag__goal" data-tag-goal></span>
            </div>
          </div>
        </div>
        <button class="restart" type="button" hidden data-restart>Fill it with the market mix</button>
      </div>

      <ul class="lines" aria-label="Fruit in your bag" data-lines></ul>

      <div class="sums">
        <p class="sums__row"><span>Subtotal</span><span class="sums__n" data-sub></span></p>
        <p class="sums__row"><span>Delivery</span><span class="sums__n"><s class="sums__was" data-was></s><span data-ship></span></span></p>
      </div>

      <button class="pay" type="button" data-pay>
        <span class="pay__face">
          <svg class="pay__spin" viewBox="0 0 20 20" aria-hidden="true" data-pay-spin><path d="M10 2.6a7.4 7.4 0 1 1-7.4 7.4"/></svg>
          <svg class="pay__tick" viewBox="0 0 20 20" aria-hidden="true" data-pay-tick><path d="M4.6 10.4l3.6 3.6 7.2-7.6"/></svg>
          <span class="pay__label" data-pay-label></span>
        </span>
      </button>

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

48 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 string bag cart” from Animorio and fit it into my site

What it does

On a white background a white cart 390 pixels wide: at the top Your market bag with the number of pieces, under it a cream panel with a brass rail from which, on an S hook, hangs a green cotton string bag with a wooden handle; then four lines (Blood orange, Amalfi lemon, Gala apple, Victoria plum), each with its painted fruit, the price of one, Remove, the line's total and the minus and plus buttons; at the bottom Subtotal, Delivery and the Pay button with the figure. The bag holds the fruit in the cart, one by one: three blood oranges, two lemons, two apples and four plums. When the page opens they come one after the other from above, tossed a little from the right and a little from the left, drop through the mouth of the bag, knock the ones already in and roll until they find a place; the net widens round them and the bag bobs and swings on its hook. With plus a new piece arrives the same way; with minus the line's top piece is plucked out: it rises a little, comes out in front of the net spinning and flies off to the side, while the fruit under it falls into the gap it leaves. Remove plucks out all the line's fruit, one after the other, and the line becomes a lower strip with Victoria plum removed and Undo: round Undo's arrow a ring wears away in the five seconds in which you can go back (it stops while the pointer or the focus is on the strip); Undo tosses the same fruit back into the bag. The total is written large on a kraft tag tied with string to the knot at the bottom of the net, with what is left to free delivery under it (€1.35 to free delivery): the tag swings with the bag, gives a little twist on its string at every new figure and, at €10, spins twice round (its back is plain card) and comes back with Free delivery in green, while the delivery is struck through and becomes Free. Figures that change go soft and come back sharp without moving, the quantity takes a breath. Resting the mouse on a line for a moment (or the keyboard focus) makes the other fruit in the bag see-through; the pointer, or a finger, moving over the bag pushes the net and the fruit. Pay shows Processing with a spinner, then Order placed in green with a drawn tick, while the bag is jerked up and drops back on its handles with every piece of fruit jumping; after a while Pay is back. With every line removed, Fill it with the market mix is left, which brings back the bag the page opened with.

How it works

A small world of constraints

Everything is a body with a position and a velocity: the fixed hook, the two ends of the handle, thirty knots of the net, every piece of fruit and the tag. Every frame the time (at most 1/30 of a second) is split into ten steps: gravity moves the bodies, then the positions are corrected so they keep to the constraints (position based dynamics, XPBD style) and the new velocity is the move divided by the step. When nothing has moved for about twenty frames the loop stops and nothing is drawn.

Cords that only pull

The net is a chain of knots from one end of the handle to the other: each piece of cord pulls when it is longer than its length and never pushes when it is shorter, so the empty net goes slack and the full one is taut. The two cords from the hook to the handle are springs with some give (the bag bobs), the handle is a rigid bar; the tag hangs from the bottom knot on a string that never lets it flip over.

Fruit that pushes, rolls and grips

Every piece of fruit is a circle with its own mass: two that overlap are pushed apart by the inverse of their masses, and one that goes past a piece of net is put back inside along the inward normal and pushes the two knots in proportion to where it touches. Part of the slide along the contact is taken away (the cotton grips), and what is left turns the fruit: its angular speed chases the slide divided by the radius, so it really rolls. Fruit coming in does not meet the net until it has entered through the mouth or into the triangle of the cords.

The mesh redrawn

The diamond net does not exist as an object: every frame the chain is read as rows of knots from the handle down to the bottom knot (knot k on the left with knot N minus k on the right) and the diagonal cords crossing it are drawn. The front ones bow down, the back ones bow up and are paler, so the bag looks round; in front of the fruit the cords throw a thin shadow too. The bag's shadow on the wall is a canvas shadow moved in from far away, blurred the same in every browser.

The painted fruit and the tag

Every piece of fruit is painted on the canvas: the skin (pores, blush, streaks, stalk and leaf) turns with the fruit and the light does not, because the shape is built turned and filled with a gradient that does not turn. The tag is HTML over the canvas: its box follows the string every frame, the card inside spins in 3D with GSAP and hides its words while it shows its back. A click from a script (the library's preview) is a pretend customer: one more orange and one more apple, the lemons removed, Undo, one more plum and one less, one apple and one orange less.

Values to change

NameFileDefaultWhat it changes
ITEMSscript.js[['Blood orange', 0.95, 3, 'orange'], …]The cart's lines: name, price of one in euros, how many and fruit (orange, lemon, apple or plum).
FREEscript.js10The subtotal from which delivery is free and the tag spins, in euros.
DELIVERYscript.js3.5What delivery costs below that figure, in euros.
MAXscript.js5The most pieces of one fruit.
GRAVITYscript.js900How hard the fruit falls, in pixels per second squared.
STRETCHscript.js1How much the cotton net gives under the weight (0 = it does not).
TOSSscript.js0.55The seconds a piece of fruit flies before it drops into the bag.
UNDOscript.js5The seconds Undo stays after a line is removed.
BRUSHscript.js1How much the pointer pushes the bag and the fruit (0 = not at all).

Try them in the Playground

When to use it

For the cart of a greengrocer, a farmers' market, a grocery or anyone selling loose pieces (fruit, vegetables, bread, sweets): a list with a total, a threshold for free delivery and a live, playful preview of what is being taken home.

On phones

At 390 pixels the cart is 358 wide and the drawing scales down by the same amount, the canvas redrawn at the screen's resolution; every button takes a finger and does not wait for a double tap (touch-action manipulation). A finger moving over the bag pushes it like the mouse, and the page still scrolls vertically (touch-action pan-y). The other fruit goes see-through only with the mouse or the keyboard, never after a tap.

With reduced motion

No tosses, bounces or swinging: a new piece is laid on top of the pile, the physics is run to rest at once without drawing the steps in between, and the fruit fades in already in its place; one removed fades out and the rest have already settled. The tag does not spin: its words change with a fade. Figures change without blurring, lines without animating their height, the pointer does not push the bag and Pay does not lift it.

Pitfalls

  • A piece of fruit tossed from outside counted as inside the bag as soon as it passed under the line of a cord, even while it was still outside the net: the net then pulled it in through the mesh with huge corrections, and the bag started going round the hook. Now it counts as inside only between the two ends of the handle, and every push from the net is held to 4 pixels a step.
  • With fruit tossed from far to the side the bag took so much push that it swung for seconds and the next pieces missed the mouth: now they come from above, aim at the mouth where it is at that moment, and the handle and the fruit already in are slowed down more.
  • When the bag suddenly got lighter (all the plums removed) it bounced up and the light tag went over the knot and showed upside down: the string is now stiff at the knot and never goes past 49 degrees from upright.
  • With the most fruit the pile came out of the mouth and over the top edge of the panel: the bag was made wider and longer, the fruit smaller and the most of one kind brought down to five.
  • A minus pressed right after a plus plucked out the piece that had not been tossed yet, but its planned toss still went off and put it back in the bag: the toss now checks that the fruit still exists.
  • The back of the spinning tag showed its words mirrored: while it shows its back the words are hidden, and the new figure is written right then, when it cannot be seen.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Figtree (font variabile) 5.3.0.

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

Testing

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