Skip to content
animorio
IT
All components

The fabric picker

Pickers, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 21 KBGSAP 28 KBFreeDownload the zip

The fabric of a made-to-order sofa: Linen, Velvet, Wool, Denim and Corduroy, five small swatches cut with pinking shears. Beside them, hanging from an oak clamp on a brass rail, a large swatch of real cloth, simulated point by point, with its weave and the light on its folds: the velvet glows on the flanks of its folds, the corduroy catches the light in bands. Choosing another fabric, the new swatch flips out of the clamp towards you, falls over the old one pushing it back and settles swaying; the pointer or a finger blows on it like a breeze.

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 fabric picker</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/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the fabric picker of a made-to-order sofa: five real radio buttons, Linen, Velvet, Wool, Denim and Corduroy,
         each a small swatch cut with pinking shears. Beside them a large swatch hangs from an oak clamp on a brass rail,
         and it is real cloth: a grid of points moved by Verlet integration, drawn on a canvas triangle by triangle with
         the weave of its fabric and the light on its folds (the velvet glows on its flanks, the cord catches the lamp
         across its wales). Choosing another fabric, the new swatch flips out of the clamp, falls over the old one and
         settles swaying; the pointer, or a finger, blows on it like a breeze -->
    <div class="fabric" data-fabric data-autoclic>
      <div class="fabric__hang" data-hang>
        <canvas class="fabric__cloth" aria-hidden="true" data-cloth></canvas>
      </div>

      <fieldset class="fabric__pick">
        <legend class="sr">Upholstery fabric</legend>
        <div class="fabric__head" aria-hidden="true" data-rise>
          <span class="fabric__title">Upholstery</span>
          <span class="fabric__hint">Free swatches by post</span>
        </div>
        <div class="fabric__name" aria-hidden="true" data-name></div>
        <p class="fabric__detail" aria-hidden="true" data-rise><span data-detail></span></p>
        <p class="fabric__spec" aria-hidden="true" data-rise><span data-spec></span><span class="fabric__price" data-price></span></p>

        <div class="fabric__chips" data-chips>
          <label class="chip" data-chip>
            <input class="chip__radio" type="radio" name="fabric" value="linen">
            <span class="chip__lift">
              <canvas class="chip__cloth" aria-hidden="true"></canvas>
              <svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
                <defs><mask id="sew-linen" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
                <rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-linen)"/>
              </svg>
            </span>
            <span class="chip__name">Linen</span>
            <span class="sr" data-about="detail">Washed linen · Oat</span>
            <span class="sr" data-about="spec">290 g/m² · 30,000 rubs</span>
            <span class="sr" data-about="price">Included</span>
          </label>
          <label class="chip" data-chip>
            <input class="chip__radio" type="radio" name="fabric" value="velvet">
            <span class="chip__lift">
              <canvas class="chip__cloth" aria-hidden="true"></canvas>
              <svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
                <defs><mask id="sew-velvet" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
                <rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-velvet)"/>
              </svg>
            </span>
            <span class="chip__name">Velvet</span>
            <span class="sr" data-about="detail">Cotton velvet · Bottle green</span>
            <span class="sr" data-about="spec">420 g/m² · 50,000 rubs</span>
            <span class="sr" data-about="price">+ €220</span>
          </label>
          <label class="chip" data-chip>
            <input class="chip__radio" type="radio" name="fabric" value="wool">
            <span class="chip__lift">
              <canvas class="chip__cloth" aria-hidden="true"></canvas>
              <svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
                <defs><mask id="sew-wool" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
                <rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-wool)"/>
              </svg>
            </span>
            <span class="chip__name">Wool</span>
            <span class="sr" data-about="detail">Herringbone wool · Rust</span>
            <span class="sr" data-about="spec">380 g/m² · 40,000 rubs</span>
            <span class="sr" data-about="price">+ €180</span>
          </label>
          <label class="chip" data-chip>
            <input class="chip__radio" type="radio" name="fabric" value="denim">
            <span class="chip__lift">
              <canvas class="chip__cloth" aria-hidden="true"></canvas>
              <svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
                <defs><mask id="sew-denim" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
                <rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-denim)"/>
              </svg>
            </span>
            <span class="chip__name">Denim</span>
            <span class="sr" data-about="detail">Selvedge denim · Indigo</span>
            <span class="sr" data-about="spec">460 g/m² · 60,000 rubs</span>
            <span class="sr" data-about="price">+ €120</span>
          </label>
          <label class="chip" data-chip>
            <input class="chip__radio" type="radio" name="fabric" value="cord">
            <span class="chip__lift">
              <canvas class="chip__cloth" aria-hidden="true"></canvas>
              <svg class="chip__stitch" viewBox="0 0 64 64" preserveAspectRatio="none" aria-hidden="true">
                <defs><mask id="sew-cord" maskUnits="userSpaceOnUse" x="-4" y="-4" width="72" height="72"><rect class="chip__sew" x="1.5" y="1.5" width="61" height="61" rx="5" pathLength="1"/></mask></defs>
                <rect class="chip__seam" x="1.5" y="1.5" width="61" height="61" rx="5" mask="url(#sew-cord)"/>
              </svg>
            </span>
            <span class="chip__name">Corduroy</span>
            <span class="sr" data-about="detail">Cotton corduroy · Mustard</span>
            <span class="sr" data-about="spec">340 g/m² · 35,000 rubs</span>
            <span class="sr" data-about="price">+ €160</span>
          </label>
        </div>
      </fieldset>
    </div>
  </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.

58 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 fabric picker” from Animorio and fit it into my site

What it does

On a white background, on the left, a brass rail with two brackets screwed to the wall; over the rail a hook of brass wire holds an oak clamp with its grain, the slot down its middle and two brass caps, and from the clamp hangs a large swatch of cloth with zigzag edges and a woven label sewn near the hem. The swatch casts its soft shadow on the wall, down to the right. On the right: Upholstery and Free swatches by post in small, the fabric's name large in Fraunces, under it the description, the weight, the rub count and the extra price, and at the foot five small square swatches cut in zigzag, each with its name. Every fabric has its weave painted: oat linen with its uneven yarn, bottle green velvet with its pile, rust herringbone wool with fibres of other colours, indigo denim with the diagonal of its twill and the white selvedge with its red thread, mustard corduroy with its round wales. On opening the rail drops into place with a bounce and the first swatch falls from the clamp; the name comes in letter by letter and the small swatches rise one after another. Choosing another fabric the new swatch comes out of the clamp held out towards you, falls, passes in front of the old one and pushes it back, then swings back and forth and stops; the clamp rocks on its hook towards the side of the fabric chosen. Meanwhile the old name leaves letter by letter and the new one comes in with a small bounce, changing the weight, softness and optical size of the face to suit the fabric (light for linen, soft for velvet, full and crisp for denim); the lines under it change with it. Round the small swatch chosen a dashed seam sews itself, and the one left is unpicked. With the mouse near the swatch the pointer blows: the cloth fills and sways in gusts, harder if you move it fast, and a click is a puff. Under the pointer the small swatches lift and tilt on a spring. The arrow keys change fabric as real radios do; the focus shows a ring. On a phone the swatch sits over the choice, and a finger tapping or dragging on the swatch blows on it.

How it works

A hanging grid of points

The swatch is a grid of 13 by 17 points in 3D, hung by its first row from the clamp. Between the points there are links along the rows, along the columns and across both diagonals of each cell (so the cloth neither stretches nor shears) and a softer pair two points apart, which resists bending. The rest lengths are measured on a swatch already draped a little, with soft folds that deepen towards the hem: hanging still it is never a flat sheet.

Verlet, in fixed steps, a fabric at a time

Each point has its position now and the one before: its speed is the difference, held back by the air, and gravity and the blowing are added times the square of the step. Then the links are brought back to their length twelve times a step, in fixed steps of 1/120 of a second (the frame capped at 1/30). Every fabric has its own weight, stiffness and air: velvet is heavy and falls soft, denim is stiff and makes wide folds, linen is light and sways more.

The new swatch falls over the old one

The new swatch is born held out towards you, turned 93 degrees about the line of the clamp, with a small twist and a push downwards: gravity does the rest. The clamp holds the new one 2.2 pixels in front and the old one 2.2 behind; where they meet, point by point, the front one stays in front and the back one gives more, so the new one, swinging, pushes the old one back. The old one is taken away once the new one covers it, still. When everything is nearly still the swatch eases into its rest pose (worked out once for each fabric by letting it hang a few seconds) and the drawing stops.

Cloth drawn in triangles

Each fabric is painted once on a canvas off screen, twice as fine: the weave, the zigzag edges, the label; and a second time its wrong side. Each point is projected in perspective towards an eye a little over the rail. Then every cell is drawn in two triangles, each with its piece of the painting carried by the affine transform from the three flat points to the three points now, clipped a little wider so no seams show; the cells are sorted by depth and a triangle turned over shows the wrong side.

A light for each fabric

Every frame the normal of each point gives two maps as large as the grid, laid on the painting with smoothing: the shade in multiply and the sheen in screen. Linen and denim have a soft highlight; wool a fuzz on its edges; velvet is dark where it faces you and glows where the fold turns away (1 minus the normal dotted with the view, to the power 2.2); corduroy uses the Kajiya and Kay hair model with the tangent along the wales, so it shines in bands. The shadow on the wall is the swatch's outline shifted by its distance from the wall, drawn four times smaller and enlarged, so it is soft.

The words and the small swatches

The name is set in Fraunces, a variable face: every fabric has its own weight, softness (the SOFT axis) and optical size, and GSAP carries them from one to the other while the letters come in with a bounce. The small swatches are real radios inside their labels: the arrows work, and a screen reader also hears the description, the weight and the price. The seam round the small swatch chosen is a dashed rectangle with a mask whose stroke (pathLength 1) moves on like the needle.

Values to change

NameFileDefaultWhat it changes
STARTscript.js1The fabric chosen on opening: 0 Linen, 1 Velvet, 2 Wool, 3 Denim, 4 Corduroy.
BREEZEscript.js1How hard the pointer blows on the swatch: 0 not at all, 2 a gust.
DROPscript.js1How heavy the falling cloth is: more, a quicker drop and a sharper swing.
AIRscript.js1How much the air holds the cloth back: less, it sways for longer.
STIFFscript.js1How much every fabric resists bending: 0 soft as silk, 2 stiff as canvas.
SHEENscript.js1How much light the velvet and the corduroy catch on their folds.
WALLscript.js30How far the swatch hangs from the wall, in pixels: farther, its shadow falls farther off.
TOURscript.js2In the preview's tour, the seconds from one fabric to the next.

Try them in the Playground

When to use it

For choosing the fabric of a sofa, an armchair, a headboard or a made-to-measure shirt, where people want to see and nearly touch the cloth before ordering; also for a shop selling fabric by the metre or curtains. The large swatch shows one fabric at a time: with more than six or seven choices a dropdown beside it is better. The prices and figures here are made up: take them from your catalogue.

On phones

The swatch sits over the choice, drawn smaller (270 pixels tall instead of 380), and the physics is the same. The five small swatches sit in one row as wide as the screen, at 390 pixels with room to spare. A finger touching the swatch is a puff, and dragging it sideways the breeze follows; scrolling up and down stays free.

With reduced motion

The swatch hangs still in its rest pose, the pointer does not blow, the clamp does not rock, the small swatches neither lift nor tilt. Changing fabric the new swatch fades in over the old one, the name and the lines change with a fade and the seam appears whole.

Pitfalls

  • With rest folds 5 pixels deep and the lamp nearly in front, the still velvet looked like flat felt: the light depends on the slope of the folds. Folds of 10 pixels and the lamp moved to the side, more raking, show the folds even when still.
  • The dashed seam of the small swatches not chosen left a speck at the corner: with the mask dashed «1 1» and the offset at 1 the last bit of the path fell on the start of the next dash. With «1 2» and the offset at 1.5 (and −1.5 to unpick) both ends sit in the middle of the gap.
  • The shadow on the wall drawn cell by cell showed a grid of light lines: the smoothed edges of each quad let the background through. Now it is a single outline, the ring of the edge points, filled and stroked.
  • The preview's video stops when the demo comes back as it was: the cloth, settled by the physics, ended in a slightly different pose each time. When nearly still it now eases in half a second into the rest pose worked out once for each fabric, always the same.
  • The new swatch and the old one pass through each other if only their clamps are looked at. A constraint for each point keeps them in order: where the front one would end up behind, both move, the back one more.

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 21 KB; the libraries: GSAP 28 KB.

Testing

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