Skip to content
animorio
IT
All components

The drop beaker upload

Loading, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

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.

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

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.

50 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 drop beaker upload” from Animorio and fit it into my site

What it does

On a black background, alone in the middle, a glass beaker with its spout on the left and white marks printed on the right, one every 5%, longer and numbered at 25, 50, 75 and 100%; above it hangs a dropper with a rubber bulb and a glass tube full of the first file’s colour. Under the beaker the words 6 files · 84 MB and Ready to upload, and the Upload files pill. Pointing at the pill makes the drop on the dropper’s tip swell a little, ready to fall. Pressed, the upload starts: the line says Uploading 6 files, under it the file’s name and the megabytes that have arrived (hero-render.png · 31 of 84 MB), on the right the big percentage. Every chunk of data, about 6 MB, is a drop: it swells at the tip while it travels, then stretches on a thinning neck, pinches off and falls, a little longer as it goes. Touching the liquid it melts into the surface, which dips and springs back, sinks into the liquid as a cloud of its colour and starts a wave that runs to the glass and bounces back, while a jet jumps up and a few droplets fly to the sides. The colour in the dropper’s tube goes down with every drop; when a file is done the dropper moves over a little on a spring and fills with the next file’s colour, so the files stay in layers in the beaker (the small file is a thin line). The marks the liquid has passed turn brighter, and under the beaker the liquid throws a light of its colour on the table. In the demo one upload in two stops halfway: the hanging drop turns cloudy and grey, shudders and stays there, the line becomes Upload paused in red with Connection lost on hero-render.png and the pill says Retry; Retry draws the drop back into the dropper, which clears, and the chunk starts again. At the end the dropper lifts away and disappears, the waves die down fast, a glint of light runs across the glass on a slant and the line becomes Upload complete with a green tick that draws itself; Upload again empties the beaker and starts over. During the upload the pill, pale with just a ring, says Cancel: the beaker empties and goes back to rest.

How it works

The drops are metaballs

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

How a drop pinches off

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.

The surface is a chain of springs

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.

The level and the layers

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.

Failure, the end and starting again

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.

Those who cannot see, and the demo

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.

Values to change

NameFileDefaultWhat it changes
SECONDSscript.js7How long the demo’s upload takes, in seconds (a real one follows the bytes as they arrive).
FILESscript.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).
CHUNKscript.js6The megabytes in a drop: a file is cut into drops of about this size (at least one per file).
FAIL_EVERYscript.js2In the demo, one upload in how many goes wrong; 0 never.
FAIL_ATscript.js0.5How far the upload that goes wrong gets, from 0 to 1.
DROPscript.js5.6The radius of a drop of CHUNK megabytes, in drawing units (the beaker is 152 wide); smaller chunks make slightly smaller drops.
GRAVITYscript.js1500The gravity on the drops and the splashes, in units per second squared: stronger, the drops go in faster and make taller waves.
WAVEscript.js[210, 1.8]The surface: how fast a wave runs (units a second) and how quickly it dies down.
SPLASHscript.js3The droplets that jump up when a drop goes in: the first is the straight jet, the others fly to the sides.
WORDSscript.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.
LABELSscript.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.

Try them in the Playground

When to use it

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.

On phones

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

With reduced motion

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.

Pitfalls

  • The cloudy drop of a failure, with a fine, strong grain, looked like a pebble or a biscuit rather than a liquid: the grain is now coarser and lighter (±15%) and fixed to the drop, so it does not crawl as the drop shudders.
  • The line with the file’s name and the megabytes, changed every frame with a fade, was always halfway through its fade and looked dim: it now fades only when the file changes, and the megabytes are updated directly.
  • At the end it read 99%: the level follows the bytes and the end came with a hundredth still to go. The end now takes the level to full and writes 100%.
  • Two paths of the glass passed together to fill the thickness of the bottom: the second began with beginPath and wiped out the first, so the whole inside was filled. The second is now added to the first and the bottom is filled with evenodd.
  • With the chain of springs pulled too hard towards its rest height, a drop’s wave barely showed and died before reaching the glass: the push is now three times stronger and the spring towards rest half as strong, and the wave bounces off the walls.
  • The side splashes, all leaving with the drop, looked like scattered specks: the first is now a straight, bigger jet that leaves 90 thousandths later, as the crater springs back.

Libraries

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.

Testing

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