Skip to content
animorio
IT
All components

The composing stick loader

Loading, Text, Transitions, PhysicsGSAP, Canvas, CSS onlyAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

Generating a PDF invoice shown as setting metal type by hand: press Generate invoice and the lead sorts, their letters raised and back to front, drop one after another into the steel composing stick, are tapped tight and every full line slides down into the brass galley. Then the roller inks the forme, the press comes down with a thump and the sheet lifts and turns over: the invoice printed the right way round, in real-looking ink. Every other run a wrong sort is picked, jumps out, and Retry sets the right one.

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 composing stick loader</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/instrument-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the composing stick loader: an invoice being generated, shown as a page of metal type being set by hand. Pressed,
         the sorts drop one after the other into the steel composing stick, their letters raised and back to front; a
         full line is tightened with a tap and slides down into the brass galley. When the forme is complete a roller
         inks it, the platen comes down with the sheet and lifts, and the sheet turns over with the invoice printed the
         right way round. The metal is drawn in HTML and CSS by script.js, the print is painted on a canvas -->
    <section class="press" aria-label="Invoice generation" data-press>
      <div class="press__scene" role="progressbar" aria-label="Invoice generation" aria-valuemin="0" aria-valuemax="100"
           aria-valuenow="0" aria-valuetext="Ready to generate" data-scene>
        <div class="press__fit" data-fit>
          <div class="press__box" aria-hidden="true" data-box>
            <div class="galley">
              <div class="galley__floor"></div>
              <div class="galley__rim galley__rim--top"></div>
              <div class="galley__rim galley__rim--side"></div>
              <div class="galley__rim galley__rim--foot"></div>
            </div>
            <div class="stick">
              <div class="stick__back"></div>
              <div class="stick__bed"></div>
              <div class="stick__clamp"></div>
              <div class="stick__knee"></div>
            </div>
            <div class="press__type" data-type></div>
            <div class="stick__lip"></div>
            <div class="roller" data-roller><div class="roller__drum" data-drum></div></div>
            <div class="sheet__shadow" data-sheet-shadow></div>
            <div class="sheet" data-sheet>
              <canvas class="sheet__face sheet__face--back" data-back></canvas>
              <canvas class="sheet__face sheet__face--front" data-front></canvas>
            </div>
            <div class="platen" data-platen><div class="platen__ribs"></div></div>
            <div class="press__fly" data-fly></div>
          </div>
        </div>
      </div>
      <p class="press__status" aria-hidden="true">
        <svg class="press__tick" viewBox="0 0 24 24" data-tick><path d="M5.5 12.6l4.2 4.2 8.8-9.4" pathLength="1"/></svg>
        <svg class="press__warn" viewBox="0 0 24 24" data-warn><path d="M12 7.5v6M12 16.6v.1" pathLength="1"/><path d="M10.3 3.9L2.6 17.6a2 2 0 0 0 1.7 3h15.4a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
        <span class="press__swap" data-status>Ready to generate</span>
        <span class="press__pct" data-pct></span>
      </p>
      <div class="press__buttons">
        <button class="pill is-quiet" type="button" hidden data-again><span class="pill__label">Generate again</span></button>
        <button class="pill" type="button" data-go data-autoclic><span class="pill__label" data-label>Generate invoice</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.

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 composing stick loader” from Animorio and fit it into my site

What it does

On a black background, alone in the middle, a steel composing stick seen from above and a little from the front: the back plate, the fixed knee on the right, the clamp that sets the measure on the left and the polished front ledge. Under it is a brass galley with its rims, then the line Ready to generate and the Generate invoice pill. Pointing at the pill brings the first sort into view above the knee, waiting at a slight tilt. Pressed, the sorts drop one at a time into the stick starting from the knee, turning as they fall: little blocks of lead with the face catching the light, the body with its nick and on the face the letter raised, back to front, in a variable serif. Each sort lands a hair loose and the line jolts; when the line is set, the quads (metal spaces lower than the type) slide in from the left to fill the measure, a tap tightens the line with a spring and a glint runs over the metal, and the line lifts out of the stick and drops into the galley onto its lead, with a small bounce. The line of words says Laying out INV-0412 and the percentage. With the four lines in (INVOICE INV-0412, Marlow & Finch, Due 30 October, Total €1,840.00, each at its own weight), the roller passes and the letters turn black with ink line by line (Rendering PDF). Then the cast iron platen comes down with the sheet, lands with a thump that shakes everything and lifts: you see the back of the sheet, the letters faintly raised and back to front; the sheet lifts and turns over, and on the front is the invoice printed the right way round, with the grain of the paper, the darker rim of ink, the specks where the ink was starved and the bite of the impression. The line becomes Invoice ready with a green tick that draws itself, the Download PDF pill (then Downloaded) and next to it Generate again, which takes the sheet away, distributes the type and starts over. Every other run, a d drops into the third line where a b should be (back to front they are easy to mix up): the sort turns red, the line shakes, it jumps out of the stick spinning and falls away; the line says Glyph mismatch on line 3 with a warning triangle and the Retry pill sets the right sort and goes on from there.

How it works

The width of every sort

Every sort is exactly as wide as its letter’s advance, measured with measureText on a canvas in the variable font Fraunces, at its line’s weight (720 for the heading, 470 for the client…). The line is set from the knee leftwards in reading order, so the forme is the mirror of the page; if a line does not fit the measure, its size goes down until it does. What is left up to the clamp is filled with quads, lower blocks with no letter.

A block of lead in CSS

Every sort is a div with two faces: the face on top, 22 pixels high, with a gradient tilted to the light and a pale edge top left, and the body in front, darker, with the nick drawn by two gradient lines and a shadow. The letter sits on the face with scaleX(-1), raised by three text-shadows (light top left, shadow bottom right, a soft shadow). Every sort has a brightness of its own, between 0.9 and 1.08, like worn type; the glint of the tap is a soft-light ::after moved by a CSS variable that GSAP animates.

The whole job in one timeline

One GSAP timeline holds everything: the drops (each turning straight with power2.in and ending a hair loose), the line’s jolt with elastic, the quads, the tap, the line’s trip into the galley with a bounce, the roller, the platen, the sheet. The percentage comes from callbacks placed where each sort lands. In the run that goes wrong the wrong sort goes into the timeline before the right one, followed by an addPause: the timeline stops by itself, and Retry plays it on.

The roller and the press

The roller travels over the galley with power1.inOut, and the moment its middle passes over each line is found by inverting the curve: there the line gets the class that turns its letters black. The platen comes down from bigger and higher (closer to the eye) with power3.in, and as it lands the whole scene drops two pixels and springs back. The sheet has two faces in CSS 3D with backface-visibility: turning it 180 degrees about the vertical axis undoes the mirror again, so the print reads the right way; the shadow under it narrows and widens as it turns.

The print on a canvas

The page is painted once per job, from the same sort widths mirrored around the middle: the paper (grain pixel by pixel on a canvas of its own, a few fibres, a shade at the edges), the bite of the impression (the letter in light shifted down right and in dark up left), then the ink on a canvas of its own, with a different opacity per letter, the squeezed rim from a thin stroke and thousands of specks taken out with destination-out, laid on the paper with multiply. The back is the same paper with the letters back to front, barely visible.

Values to change

NameFileDefaultWhat it changes
SECONDSscript.js6How long setting the lines takes, in seconds; inking and printing add about two seconds (a real generation follows the server’s work).
LINESscript.js['INVOICE INV-0412', 'Marlow & Finch', 'Due 30 October', 'Total €1,840.00']The invoice’s lines, four at most: a line too long for the stick gets smaller by itself.
WEIGHTSscript.js[720, 470, 420, 640]The type weight of each line, from 100 to 900: the font is variable, so every weight is real, in the print too.
FAIL_EVERYscript.js2In the demo, one generation in how many goes wrong (0 never).
FAIL_LINEscript.js3Which line the wrong sort drops into.
FAIL_SORTscript.js['b', 'd']The right sort and the wrong one that drops in its place; if the line has no right one, a letter half way along goes wrong.
LABELSscript.js{ busy: 'Generating…', done: 'Download PDF', saved: 'Downloaded', retry: 'Retry' }The button’s words while it works, at the end, after the download and after the error; the one at rest, Generate invoice, is changed in index.html.
WORDSscript.js{ set: 'Laying out INV-0412', render: 'Rendering PDF', done: 'Invoice ready', fail: 'Glyph mismatch on line' }The line under the drawing while it sets (with the percentage), while it prints, at the end and after the error (followed by the line’s number); the one at rest, Ready to generate, is changed in index.html.

Try them in the Playground

When to use it

For a wait of a few seconds that ends with a document: generating an invoice, a quote, a receipt or a contract as a PDF, exporting a laid-out report, preparing a print file. It suits invoicing tools for freelancers and studios, billing or publishing apps, where typography is part of the product. For a wait of a moment it is too long.

On phones

The drawing is 360 pixels wide and scales to the width it gets: at 390 it fits whole with 16 pixels on each side, and the letters stay sharp because they are real text; the print canvas paints at least at twice the density. There is room above for the falling sorts. The pills are 44 high and squash and spring back under the finger; the sort waiting above the knee shows with the mouse and the keyboard.

With reduced motion

Nothing drops and nothing moves: every line fades in already in its place in the galley, one after the other, the letters turn black line by line with no roller, and at the end the printed sheet fades in already the right way round, with no press and no turn. The wrong sort turns red and fades away. The percentage, the words and the tick update with fades.

Pitfalls

  • The paper grain made with getImageData on the page canvas made Chrome complain (repeated readbacks): the grain is now made on a canvas of its own with createImageData and laid on with drawImage.
  • The first sort waiting when the mouse comes over, placed just above the knee, almost vanished behind it and only a sliver showed: it now sits higher and a little to the left, tilted, well clear of the steel.
  • With few specks taken out the print looked like computer text on beige paper: there are nearly twice as many, and with a different opacity per letter and the squeezed rim the page looks like real ink.
  • The line dropping into the galley has to pass over the stick’s ledge, which covers the bottom of the sorts while they are in the stick: the lines’ container has no z-index (it would shut every line into one layer) and each line changes its own on the way.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 15 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