Skip to content
animorio
IT
All components

The leashed nib

Drawing, Physics, SVGGSAP, CanvasAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

A pad to sign or sketch on, in the minimal style, with a lazy brush: the ink does not come from the hand but from a vermilion nib tied to the hand by a thin thread. While the thread is slack the nib stays put; taut, it tows the nib behind it, and the line comes out smooth even if the hand shakes. Undo makes the nib reel its last line back in, Clear pulls every line tight like a thread until it lies straight and slips away, and a slider changes the length of the thread.

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 leashed nib</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/full.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- a pad to sign or sketch on with a lazy brush: the ink does not come from the hand but from a nib tied to it by a
         thread of fixed length. While the thread is slack the nib stays put; pulled taut, it tows the nib, and the line
         comes out smooth however the hand shakes. Undo makes the nib reel its last line back in, Clear pulls every line
         tight like a thread until it lies straight and slips away, and the slider sets the length of the thread -->
    <section class="sheet" aria-labelledby="sheet-title" data-card data-autoclic>
      <header class="sheet__head">
        <h2 class="sheet__title" id="sheet-title">Sign for your parcel</h2>
        <div class="sheet__acts">
          <button class="act" type="button" data-undo disabled><span class="act__word">Undo</span></button>
          <button class="act" type="button" data-clear disabled><span class="act__word">Clear</span></button>
        </div>
      </header>

      <div class="pad" tabindex="0" role="application" aria-roledescription="signature pad" aria-label="Signature pad, empty" aria-describedby="pad-help" data-pad>
        <canvas class="pad__ink" aria-hidden="true" data-canvas></canvas>
        <svg class="pad__over" aria-hidden="true" focusable="false" data-over>
          <rect class="pad__frame" pathLength="1" data-frame></rect>
          <line class="pad__base" pathLength="1" data-base></line>
          <g class="dim" data-dim>
            <line class="dim__end" data-dim-a></line>
            <line class="dim__end" data-dim-b></line>
          </g>
          <path class="leash" data-leash></path>
          <g class="hand" data-hand>
            <line x1="-5.5" y1="0" x2="5.5" y2="0"></line>
            <line x1="0" y1="-5.5" x2="0" y2="5.5"></line>
          </g>
          <g class="nib" data-nib>
            <rect class="nib__shape" x="-3.6" y="-3.6" width="7.2" height="7.2" data-nib-shape></rect>
          </g>
        </svg>
        <span class="pad__hint" data-hint>Sign above the line</span>
      </div>

      <div class="sheet__foot">
        <label class="leash-word" for="leash-range"><span data-word>Leash</span></label>
        <div class="range-wrap">
          <input class="range" id="leash-range" type="range" min="0" max="60" step="1" value="22" data-range>
          <span class="range__ticks" aria-hidden="true" data-ticks></span>
        </div>
        <output class="leash-val" for="leash-range" data-val>22 px</output>
      </div>

      <p class="sr" id="pad-help">Draw with a mouse, a pen or a finger. With the keys, the arrows move the hand, Space puts the nib down and lifts it.</p>
      <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.

21 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 leashed nib” from Animorio and fit it into my site

What it does

On a white background, top left Sign for your parcel and on the right two pill buttons with a thin border, Undo and Clear; below, a pad drawn with a thin line, with a grey baseline and under it Sign above the line. At the start of the line sits the nib, a small hollow vermilion diamond. At the bottom the word Leash, a thin slider with a tick every ten pixels and the length of the thread, 22 px. As the page opens the pad draws itself from the corner, the baseline runs from left to right and the nib drops onto the line, goes a pixel past and settles. With the mouse over the pad the pointer becomes a thin cross, tied to the nib by a grey thread 22 pixels long: while the thread is slack the nib does not move and the thread hangs a little; when the hand moves away the thread pulls taut, goes straight and black and tows the nib, which turns towards the hand. Pressed, the nib fills and writes: the ink, almost black, is one or two pixels and thins when the nib goes fast; a shake of the hand shorter than the thread never reaches the sheet, and corners become curves. Leaving the pad, the thread reels back into the nib. Undo makes the nib run backwards over its last line, towed by the thread running ahead of it, and the line disappears behind it. Clear pulls every line like a thread, the last first: the small curls go first, then the big bends, until the line is a straight line between its two ends, which rings once like a plucked thread and slips away towards its end; the nib goes home. Moving the slider, the word Leash widens or narrows with the length, and on the sheet the thread lies out straight from the nib, as long as the slider says, with a tick at each end like a dimension.

How it works

The lazy brush

The hand (the mouse, the pen, the finger, the keys) and the nib are two different points. At every move the distance between them is measured: if it is less than the length of the thread the nib stays where it is; if it is more, the nib is brought along the line towards the hand, exactly a thread away. That is the whole trick: the nib only travels the long part of the movement, and shakes shorter than the thread are lost. For a quick stroke the points the browser gathers between two frames are used too (getCoalescedEvents).

The thread is a chain

The thread you see is a chain of 16 links moved with Verlet (each link remembers where it was, and the difference is its speed) with a little gravity, SAG, and then held to its length: a thread does not stretch, so every link too far from its neighbour is brought closer, 14 times a step, never pushed away. Its ends are the hand and the nib. Taut, the thread is drawn dead straight and its colour goes from grey to the black of the ink; the nib, a diamond, turns to where the thread pulls it.

The ink

Every stroke is a list of the nib’s points with their widths: the speed of the nib between two points takes the width from WIDTH[1] towards WIDTH[0] as it nears SPEED, a little at a time. It is drawn on a canvas at the screen’s density, with curves through the middle points, each piece as wide as its ends; every stroke remembers which part of it shows (from a to b, even halfway between two points), so Undo and Clear can shorten it from either end.

Undo reels in

Undo takes the visible end of the last stroke back to its start with power2.inOut, in a time that grows with the length; the nib always sits on that end, and the far end of the thread is put on the stroke itself, a thread’s length further back, measured along the curve: it looks like a hand pulling the nib backwards along its own way. Past the start the thread goes on straight in the direction of the first piece.

Clear pulls the threads

Every stroke is resampled into evenly spaced points, and how far each point strays from the straight line between the two ends is broken into waves (a discrete sine transform). Then every wave k dies away as exp(−k² τ), with τ growing with time: it is the heat equation, the one that smooths a curve, which is why the small curls vanish at once and the big bends last. Straight, the stroke rings once (a damped sine of a couple of pixels) and slips away towards its end, the last stroke first, staggered.

Keys, slider and the preview

The pad takes the focus: the arrows move the hand by 8 pixels (24 with Shift), Space or Enter put the nib down and lift it, Escape lifts it; the staircase of the keys turns into a curve by itself, thanks to the thread. The slider is a real range input: the word Leash takes its width (the wdth axis of Roboto Flex, from 40 to 151) from the length, with back.out. A click from a script (the library’s preview) signs with a pretend hand that shakes, lets the thread out and underlines in a zigzag, then does Undo, puts the thread back and does Clear.

Values to change

NameFileDefaultWhat it changes
LEASHscript.js22The length of the thread between the hand and the nib, in pixels, the one the slider starts at: 0 draws from the hand.
LEASH_MAXscript.js60The longest thread the slider gives, in pixels.
WIDTHscript.js[0.9, 2]The thinnest and the thickest the ink gets, in pixels: fast is thin.
SPEEDscript.js1.3The speed of the nib, in pixels a millisecond, at which the ink is at its thinnest.
SAGscript.js520How much the thread hangs when slack: its gravity, in pixels a second squared.
REWINDscript.js1How slowly Undo reels the last stroke back in (1 means 0.4 to 1 second, by its length).
UNTANGLEscript.js0.85The seconds Clear takes to pull a stroke straight.

Try them in the Playground

When to use it

For signing for a delivery, a contract, a consent form or an accepted quote, above all with a finger or a mouse, which shake; for a sketch pad inside a creative or note-taking app, where a calm line matters more than a faithful one.

On phones

You sign with a finger without scrolling the page (touch-action none on the pad): the nib comes to where the finger lands and the thread lets out from there as the finger moves, so the nib stays out from under the fingertip and the line shows as it is made. Lifting the finger, the thread reels back in. At 390 pixels the preview’s signature has fewer loops, so it fits the pad.

With reduced motion

The nib follows the hand on its thread, because that is how it draws, but the thread is always straight, with no swinging or hanging; Undo and Clear fade the strokes away instead of reeling them in and pulling them, the nib goes home without travelling, the pad appears without drawing itself and the word Leash changes width without the bounce.

Pitfalls

  • A real thread does not stretch but goes slack: the chain’s constraints bring two links that are too far apart together and never push two close links apart, or the slack thread would look like a spring.
  • The solver never quite reaches a dead straight thread when the ends are exactly a length apart: past 90% tension the drawn thread is brought onto the straight line, and from 99.5% it is the straight line.
  • A lifted nib does not jump: coming in from outside, the hand tows it with a small delay (an exponential approach) instead of snapping it across the pad; with a finger the nib goes straight to where the finger lands.
  • A signature made of loops smaller than the thread comes out with points: the pretend hand’s loops are more than twice as wide as the thread, so they stay loops even with the long thread.
  • With keys coming faster than the hand, the steps add up into a moving goal, or every press would start again from halfway and get lost.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile) 5.3.0.

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

Testing

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