
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
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.
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>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
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 siteEverything 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.
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.
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 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.
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.
| Name | File | Default | What it changes |
|---|---|---|---|
ITEMS | script.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). |
FREE | script.js | 10 | The subtotal from which delivery is free and the tag spins, in euros. |
DELIVERY | script.js | 3.5 | What delivery costs below that figure, in euros. |
MAX | script.js | 5 | The most pieces of one fruit. |
GRAVITY | script.js | 900 | How hard the fruit falls, in pixels per second squared. |
STRETCH | script.js | 1 | How much the cotton net gives under the weight (0 = it does not). |
TOSS | script.js | 0.55 | The seconds a piece of fruit flies before it drops into the bag. |
UNDO | script.js | 5 | The seconds Undo stays after a line is removed. |
BRUSH | script.js | 1 | How much the pointer pushes the bag and the fruit (0 = not at all). |
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.
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.
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.
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.
Tested on 13 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.