Skip to content
animorio
IT
All components

The knitted select

Menus, Transitions, HoverCanvas, GSAPAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

The size select of a merino jumper, made of knitting: the field is the ribbed collar, with the size's woven label sewn on and a ball of yarn at its end. Opening it, the list is knitted downwards row after row, with the yarn running down from the ball; choosing a size, its label flies up into the collar and the list unravels from the bottom, while the yarn, still crinkled from its stitches, goes back on to the ball.

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 knitted select</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/commissioner@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/brygada-1918@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the size of a merino jumper, chosen on a select that is knitted. The field is the jumper's ribbed collar, with
         the size's woven label sewn on and a ball of the same yarn at its end. Opening, the list is knitted downwards
         out of the collar; choosing, the label flies up into the collar and the list unravels back onto the ball -->
    <div class="size" data-select data-autoclic>
      <span class="size__label" id="size-label" data-label>Size</span>
      <div class="size__box" data-box>
        <div class="field" role="combobox" tabindex="0" aria-labelledby="size-label" aria-haspopup="listbox" aria-expanded="false" aria-controls="size-list" data-field>
          <canvas class="field__rib" aria-hidden="true" data-rib></canvas>
          <span class="sr" data-field-sr>M, chest 96 to 101 cm</span>
          <span class="tag field__tag" aria-hidden="true" data-field-tag><span class="tag__text" data-tag-text>M</span></span>
          <span class="field__text" aria-hidden="true" data-field-slot><span class="field__words" data-field-text>Chest 96–101 cm</span></span>
        </div>
        <!-- the knitting and the yarn, drawn by the script -->
        <canvas class="knit" aria-hidden="true" data-knit></canvas>
        <!-- the ball of yarn: the strands turn under a light that stays still -->
        <span class="ball" aria-hidden="true" data-ball>
          <svg viewBox="-18 -18 36 36">
            <defs>
              <clipPath id="knit-ball-clip"><circle r="15.2"/></clipPath>
              <radialGradient id="knit-ball-light" cx="0.36" cy="0.3" r="0.78">
                <stop offset="0" stop-color="#fff" stop-opacity="0.34"/>
                <stop offset="0.4" stop-color="#fff" stop-opacity="0"/>
                <stop offset="0.7" stop-color="#000" stop-opacity="0.08"/>
                <stop offset="1" stop-color="#000" stop-opacity="0.6"/>
              </radialGradient>
            </defs>
            <circle class="ball__core" r="15.2"/>
            <g clip-path="url(#knit-ball-clip)">
              <g class="ball__turn" data-ball-turn></g>
            </g>
            <circle r="15.2" fill="url(#knit-ball-light)"/>
            <circle class="ball__rim" r="15.2"/>
            <path class="ball__tail" d="M8.6 12.4c2.8 2.6 1.4 4.6 3.8 5.6" data-ball-tail/>
          </svg>
        </span>
        <ul class="list" role="listbox" id="size-list" aria-labelledby="size-label" tabindex="-1" data-list>
          <li class="opt" role="option" id="size-xs" aria-selected="false" aria-disabled="true" aria-label="XS, chest 86 to 91 cm, sold out" data-say="XS, chest 86 to 91 cm">
            <span class="tag" data-tag><span class="tag__text" data-tag-text>XS</span></span>
            <span class="opt__text"><span class="opt__chest" data-chest>Chest 86–91 cm</span><span class="opt__note">Sold out</span></span>
            <svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
          </li>
          <li class="opt" role="option" id="size-s" aria-selected="false" aria-label="S, chest 91 to 96 cm" data-say="S, chest 91 to 96 cm">
            <span class="tag" data-tag><span class="tag__text" data-tag-text>S</span></span>
            <span class="opt__text"><span class="opt__chest" data-chest>Chest 91–96 cm</span></span>
            <svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
          </li>
          <li class="opt" role="option" id="size-m" aria-selected="true" aria-label="M, chest 96 to 101 cm" data-say="M, chest 96 to 101 cm">
            <span class="tag" data-tag><span class="tag__text" data-tag-text>M</span></span>
            <span class="opt__text"><span class="opt__chest" data-chest>Chest 96–101 cm</span></span>
            <svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
          </li>
          <li class="opt" role="option" id="size-l" aria-selected="false" aria-label="L, chest 101 to 106 cm, only 2 left" data-say="L, chest 101 to 106 cm">
            <span class="tag" data-tag><span class="tag__text" data-tag-text>L</span></span>
            <span class="opt__text"><span class="opt__chest" data-chest>Chest 101–106 cm</span><span class="opt__note opt__note--low">Only 2 left</span></span>
            <svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
          </li>
          <li class="opt" role="option" id="size-xl" aria-selected="false" aria-disabled="true" aria-label="XL, chest 106 to 111 cm, sold out" data-say="XL, chest 106 to 111 cm">
            <span class="tag" data-tag><span class="tag__text" data-tag-text>XL</span></span>
            <span class="opt__text"><span class="opt__chest" data-chest>Chest 106–111 cm</span><span class="opt__note">Sold out</span></span>
            <svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
          </li>
          <li class="opt" role="option" id="size-xxl" aria-selected="false" aria-label="XXL, chest 111 to 116 cm, made to order in 2 weeks" data-say="XXL, chest 111 to 116 cm">
            <span class="tag" data-tag><span class="tag__text" data-tag-text>XXL</span></span>
            <span class="opt__text"><span class="opt__chest" data-chest>Chest 111–116 cm</span><span class="opt__note">Made to order, 2 weeks</span></span>
            <svg class="opt__check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3.2 8.7l3.1 3 6.5-7.2"/></svg>
          </li>
        </ul>
      </div>
    </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.

36 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 knitted select” from Animorio and fit it into my site

What it does

On a white page, under the word Size, sits the field of a select, 60 pixels high: it is the ribbed collar of a loden green merino jumper, its ribs standing out, with a cream woven label sewn on with the size (M) and beside it, printed on the wool, the chest measurement (Chest 96–101 cm); at the far right lies a ball of the same yarn. Pressing the field, the list is knitted under the collar: row after row, back and forth, the stockinette grows downwards, the yarn runs down from the ball along the right edge to the row on the needles, and the ball turns and gets a little thinner. Each row of the list, with its label and its words, shows when the knitting has gone past it: six sizes, from XS to XXL, with the chest measurement and, where needed, a note (Sold out, Only 2 left, Made to order, 2 weeks); at the bottom the list ends in a cast-off edge. The row under the pointer or the keys catches the light, as if the knit were lifted, and its label comes a little away from the wool; the light goes from row to row on a spring. The chosen size has its tick. The sold-out sizes have a faded label, cannot be chosen and, touched, shake their label. Choosing a size, the other labels are unpicked at once and the chosen one flies up their column into the collar, swaying and lifting a little, while the old one is pulled off; there it lands with a small bounce and its stitches appear, and the chest measurement rolls over in its slot. Meanwhile the list unravels from the bottom, row after row: the yarn comes out of the stitches still crinkled, hangs a little under the edge, runs up the side and back on to the ball, which turns backwards and fills out again; the words of each row fade as the edge gets there. It opens and closes with the keyboard too, and by typing the first letters of a size.

How it works

The stitch, drawn

Every stitch is a V of two legs of yarn. A leg is a bean tilted about 26 degrees, shaded like a cylinder: at eleven points across it the script works out how much the surface faces the light, which comes from the top left, and turns that into a gradient; then come the dark ends where it goes into the fabric, the diagonal grooves of the twisted plies, a few lighter or darker fibres and two hairs. Fourteen different stitches are drawn once and stamped at random into one strip per row (an offscreen canvas); the ribbed collar is made the same way, with narrower columns and the purl ones sunk between them.

One number to knit and unravel

The whole list hangs on one number, the rows knitted. Every frame draws the strips of the finished rows, then the row on the needles cut at the stitch it has reached (forwards on even rows, backwards on odd ones) and at the end the cast-off edge. GSAP moves the number up and down: knitting starts at once and slows into the last row (KNIT), unravelling starts at a steady pull and speeds up (UNRAVEL); opening again while it unravels, the knitting grows again from where it is.

The yarn

The yarn is a path: from the needles along the edge, round the corner, up the side to the ball, sampled every pixel and a half. Each point is pushed sideways by the crinkles, a sine with an uneven amplitude that travels with the yarn; unravelling, they are big near the edge and smooth out further up, where the yarn is pulled, and the stretch under the edge sags a little. On top go the slanted grooves of the twist, which run with the yarn; the ball turns with the rows and slims down as it unwinds.

The label that flies

On a choice, a copy of the label leaves its row and reaches the collar along a curve, with a sway and a shadow that grows as it lifts; the old one is pulled off before it arrives. The other labels go at once, so the flying one covers nothing; the rows' words instead stay until the edge reaches them, with an opacity that depends on how far away it still is. The measurement on the collar changes in a slot one line high: the old one goes out at the top as the new one comes in from below, always a line apart.

A real select

The field is a combobox with aria-expanded, aria-controls and aria-activedescendant, the list a listbox of options with aria-selected, and aria-disabled for the sold-out sizes. Arrows, Home, End, Page Up and Down move the active row, skipping the sold-out ones; Enter, Space and Alt + Up choose, Tab chooses and moves on, Esc closes; letters typed in a row look for the size that starts that way. A mousedown stopped on the list leaves the focus on the field; a press outside, or the focus going away, closes it.

Values to change

NameFileDefaultWhat it changes
KNITscript.js0.5The seconds the list takes to be knitted, from the collar to its bottom edge.
UNRAVELscript.js0.75The seconds it takes to unravel back on to the ball.
GAUGEscript.js[14, 10.5]The size of a stitch in pixels: its width, and the height of a row.
CRINKLEscript.js3.2How wavy the unravelled yarn stays, in pixels: the memory of its stitches.
LIGHTscript.js[420, 0.62]The light on the row under the pointer: its spring's stiffness and damping.
FLIGHTscript.js0.55The seconds the chosen label takes to fly up into the collar.
TYPINGscript.js0.6The seconds a typed search waits for its next letter.

Try them in the Playground

When to use it

For choosing an option in a shop of handmade things or textiles: the sizes of a jumper, the yarn, the weight of a blanket. It suits four to eight short options, which all fit in the list without scrolling; with many options (a country, a time zone) a select with a search is needed.

On phones

At 390 pixels the field is as wide as the screen minus 32 pixels and the knitting is redrawn to the new size, with stitches of the same size. A tap opens and chooses; without a pointer the light stays on the chosen size and moves to the row just touched.

With reduced motion

No knitting, no running yarn and no flying label: the finished list fades in and out, the collar's label and measurement change with a fade, the light goes straight to the row, the ball does not turn and a sold-out size, touched, dims for a moment instead of shaking.

Pitfalls

  • A stitch's V is made of two legs overlapping the rows next to it: to cut the list at a row without cutting the stitches, each row lives in a strip of its own, two rows high, and whole strips are drawn.
  • GSAP's fromTo draws its start at once: the collar's measurement vanished for the whole flight of the label and then the old one came back. The change is set up in a call and the tweens start with to.
  • With a timed fade the rows' words could not keep up with the edge speeding up as it unravels, and a label was left in mid-air under the knitting: now each row's opacity depends on how far the edge is.
  • GSAP's power2.in is a cubic: the yarn started so slowly that for half the time the list seemed to stand still. Unravelling has a curve of its own, one third linear and two thirds quadratic.
  • If the yarn's crinkles run more than half a wave per frame, they seem to go backwards like wheels in films: the yarn moves a few pixels per row, not its real length.
  • The transition of the rows coming in (opacity, translate) has the same specificity as the label's (transform) and wiped it out: for a row's label the transitions are written all together.

Libraries

GSAP 3.15.0, Commissioner (font variabile) 5.3.0, Brygada 1918 (font variabile) 5.3.0.

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

Testing

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