
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
An upload of six files made as a graduated glass beaker under a dropper: every chunk of data is a drop that swells at the tip, stretches, pinches off and falls, breaks the surface and lifts a wave that runs to the glass and back. Every file has its own colour, so the files settle in layers, and the level on the marks is the percentage. If a chunk fails its drop turns cloudy and stays hanging until Retry draws it back; at the end the dropper lifts away, the liquid calms and a glint runs across the glass: Upload complete.
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, viewport-fit=cover">
<title>The drop beaker upload</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the drop beaker: an upload of six files shown as a graduated glass beaker under a dropper. Every chunk of data
is a drop that swells at the tip, stretches, pinches off and falls; it sinks into the liquid and lifts a wave.
Every file has its own colour, so the files settle in layers, and the level is the progress, read on the
beaker's marks. The beaker, the dropper and the drops are painted by script.js on the canvas -->
<section class="becher" aria-label="File upload" data-becher>
<div class="becher__scene" role="progressbar" aria-label="Upload progress" aria-valuemin="0" aria-valuemax="100"
aria-valuenow="0" aria-valuetext="Ready to upload" data-scene>
<canvas class="becher__paint" aria-hidden="true" data-paint></canvas>
</div>
<div class="becher__info" aria-hidden="true">
<p class="becher__title">
<svg class="becher__tick" viewBox="0 0 24 24" data-tick><path d="M5.5 12.6l4.2 4.2 8.8-9.4" pathLength="1"/></svg>
<span class="becher__swap" data-title>6 files · 84 MB</span>
</p>
<p class="becher__pct" data-pct>0%</p>
<p class="becher__meta"><span class="becher__swap" data-meta>Ready to upload</span></p>
</div>
<div class="becher__buttons">
<button class="pill" type="button" data-go data-autoclic><span class="pill__label" data-label>Upload files</span></button>
</div>
<p class="sr" role="status" 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.
50 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 drop beaker upload” from Animorio and fit it into my siteThe drop on the tip, the neck, the falling drop, the splashes and the liquid’s surface are fields that add up: every ball is worth (1 − t²)² out to 1.848 times its radius, so the sum passes the 0.5 threshold right on the radius. The script works out the field pixel by pixel, but only in the box the balls reach, and writes it into an ImageData that is then copied onto the canvas. The edge is antialiased by dividing the distance from the threshold by the field’s slope, and the same slope gives the normal: the light comes from the top left with a tight highlight, and some light comes through the bottom of the drop, as in a lens.
While the chunk travels the drop grows with the square root of time, hanging from a neck that is a capsule field (the distance from a segment). When the chunk has arrived, in 0.13 seconds the drop moves down 10 units and the neck thins to nothing: the field in the middle drops below the threshold before anywhere else and the drop pinches itself off, with no hand drawn frame for the moment it lets go. Falling it stretches with its speed (up to 40%) and keeps its volume.
39 points, each tied to its neighbours and to its height at rest: a = c²/dx² · (h₋₁ + h₊₁ − 2h) − 14h − d·v, in steps of a 240th of a second, with walls that reflect. After every step the mean is taken out, so no liquid is lost: the drop’s crater pushes the rest up. A drop going in gives the points near it a push in proportion to its speed, throws a jet that jumps up 90 thousandths later (as the crater springs back) and a few droplets to the sides, which make their own small wave as they fall back. The surface is a field too, just under the threshold on its line: a drop coming close joins the liquid with a bridge, as it really does.
Every drop carries its megabytes: when it touches the liquid they are added to its file and the level follows them on an exponential spring, so it rises in small soft steps. The liquid is a single polygon, the surface on top, filled with a vertical gradient that has a band for every file, as tall as its megabytes, with a soft 3 unit seam between two files. Over it lie the clouds of the drops that just went in (radial gradients that sink and spread), the shade of the round walls and the light under the surface; the glass beaker is drawn afterwards, with the marks the liquid has passed brighter.
The chunk that goes wrong stops at 72% of its drop: the colour turns cloudy in half a second, a grain fixed to the drop makes it opaque and the drop shudders with a dying sine. Retry shrinks it to nothing inside the tip and the chunk starts over. At the end the surface’s damping goes up by 7, the dropper lifts away with a back.in and a slanted glint, a linear gradient clipped to the glass, crosses it in 1.1 seconds. The drawing loop runs only while something moves: drops, waves, the level or a tween.
The beaker is a role=progressbar with aria-valuenow and aria-valuetext updated at most every half second, aria-busy while it works and an aria-live line for the start, the failure, the end and a cancel. The button stays the same element in every state, so keyboard focus is never lost. Clicks from a script (detail 0, the library preview’s) never cancel and start again only after 2.4 seconds of Upload complete, so the thumbnail runs by itself and shows the failure too.
| Name | File | Default | What it changes |
|---|---|---|---|
SECONDS | script.js | 7 | How long the demo’s upload takes, in seconds (a real one follows the bytes as they arrive). |
FILES | script.js | [['brand-guide.pdf', 18.2], ['hero-render.png', 24.6], …] | The files to upload, each with its name and its megabytes; each one’s colour is in style.css (--drop-1 … --drop-6, then they start over). |
CHUNK | script.js | 6 | The megabytes in a drop: a file is cut into drops of about this size (at least one per file). |
FAIL_EVERY | script.js | 2 | In the demo, one upload in how many goes wrong; 0 never. |
FAIL_AT | script.js | 0.5 | How far the upload that goes wrong gets, from 0 to 1. |
DROP | script.js | 5.6 | The radius of a drop of CHUNK megabytes, in drawing units (the beaker is 152 wide); smaller chunks make slightly smaller drops. |
GRAVITY | script.js | 1500 | The gravity on the drops and the splashes, in units per second squared: stronger, the drops go in faster and make taller waves. |
WAVE | script.js | [210, 1.8] | The surface: how fast a wave runs (units a second) and how quickly it dies down. |
SPLASH | script.js | 3 | The droplets that jump up when a drop goes in: the first is the straight jet, the others fly to the sides. |
WORDS | script.js | { busy: 'Uploading', error: 'Upload paused', done: 'Upload complete' } | The words under the beaker while it uploads (followed by the number of files), when it stops and at the end; the one at rest is changed in index.html. |
LABELS | script.js | { busy: 'Cancel', error: 'Retry', done: 'Upload again' } | The button’s words while it uploads, when it stops and at the end; the one at rest, Upload files, is changed in index.html. |
For an upload of a few seconds with several files, in a product that can afford a playful touch: a creative tool, a lab or science app, a file delivery service, a lab’s samples page. The layers say which files have arrived without a list, and the drops give the wait a rhythm. For an upload of a single file, or of hundreds of small files (that would be hundreds of drops), a plain bar is better.
The component is 380 pixels wide and on a phone takes the width of the screen less 16 pixels a side (358 at 390): the drawing scales with the width and the canvas paints at the screen’s real density, up to twice, so the drops stay sharp. The metaball field is worked out only around the drops, so it stays light even on a slow phone. The pill is 44 tall: under a finger it squashes and springs back; the drop swelling on the tip, the pill that swells and the words that widen are there only with the mouse (the drop also with the keyboard).
No falling drops, waves, splashes, clouds or glint at the end, and the dropper does not move: the liquid still rises in layers, with the percentage, because it is what says how far along things are, and the colour in the dropper changes at once for every file. At the end the dropper fades away; the words change with a fade and the tick appears without drawing itself.
GSAP 3.15.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP 28 KB.
Tested on 8 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.