Skip to content
animorio
IT
All components

The blender button

Buttons, SVG, HoverGSAPMediumCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

The order button of a juice bar, in the rich style: in the round end of the raspberry pill stands a cream enamel blender with a glass jar full of strawberries, banana, kiwi and blueberries on a splash of milk. On a click the dial snaps round, the motor shudders, the blade spins and the milk forms a vortex: the fruit swirls, gets chopped and melts, the smoothie turns pink and rises while the pill fills like a timer. Then the motor stops, the smoothie sloshes, froths up and the words turn to Smoothie's ready.

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 blender button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the blender button: the order button of a juice bar, with a cream enamel blender standing in the round left end
         of the pill. Strawberries, banana and kiwi slices and a few blueberries wait on a splash of milk in the glass
         jar. A click turns the dial: the motor hums, the blade spins, the fruit is pulled into the vortex and chopped,
         the milk turns pink and rises, and the pill fills like a timer. Then the motor stops, the smoothie sloshes,
         settles under a crown of froth, and the words turn to Smoothie's ready. A second click pours a fresh one -->
    <div class="bl" data-bl>
      <button class="bl__btn" type="button" data-btn data-autoclic>
        <span class="bl__fill" aria-hidden="true"><span class="bl__timer" data-timer></span></span>
        <span class="bl__faces">
          <span class="bl__face" data-face="rest">
            <span class="bl__word" data-label-text>Blend my smoothie</span>
            <span class="bl__note" data-note-text>Berry &amp; banana</span>
          </span>
          <span class="bl__face bl__face--busy" data-face="busy" aria-hidden="true">
            <span class="bl__shine" data-busy-text>Blending…</span>
          </span>
          <span class="bl__face bl__face--done" data-face="done" aria-hidden="true">
            <svg class="bl__tick" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path data-tick d="M3 8.6 L6.6 12 L13 4.4" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
            <span class="bl__done" data-done-text>Smoothie's ready</span>
          </span>
        </span>
      </button>

      <svg class="bl__art" viewBox="0 0 120 172" width="120" height="172" aria-hidden="true" data-art>
        <defs>
          <linearGradient id="bl-enamel" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" style="stop-color:var(--enamel-deep)"/>
            <stop offset="0.28" style="stop-color:var(--enamel-light)"/>
            <stop offset="0.62" style="stop-color:var(--enamel)"/>
            <stop offset="1" style="stop-color:var(--enamel-deep)"/>
          </linearGradient>
          <linearGradient id="bl-chrome" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0" style="stop-color:var(--chrome-light)"/>
            <stop offset="0.48" style="stop-color:var(--chrome)"/>
            <stop offset="0.52" style="stop-color:var(--chrome-dark)"/>
            <stop offset="1" style="stop-color:var(--chrome-light)"/>
          </linearGradient>
          <radialGradient id="bl-dial" cx="0.4" cy="0.35" r="0.7">
            <stop offset="0" style="stop-color:var(--chrome-light)"/>
            <stop offset="0.7" style="stop-color:var(--chrome)"/>
            <stop offset="1" style="stop-color:var(--chrome-dark)"/>
          </radialGradient>
          <linearGradient id="bl-glass" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" stop-color="#cfe6ee" stop-opacity="0.55"/>
            <stop offset="0.2" stop-color="#ffffff" stop-opacity="0.08"/>
            <stop offset="0.8" stop-color="#ffffff" stop-opacity="0.04"/>
            <stop offset="1" stop-color="#cfe6ee" stop-opacity="0.6"/>
          </linearGradient>
          <linearGradient id="bl-rubber" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0" stop-color="#4a3c45"/>
            <stop offset="1" stop-color="#241b22"/>
          </linearGradient>
          <linearGradient id="bl-liquid-shade" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" stop-color="#000" stop-opacity="0.16"/>
            <stop offset="0.3" stop-color="#000" stop-opacity="0"/>
            <stop offset="0.75" stop-color="#fff" stop-opacity="0.1"/>
            <stop offset="1" stop-color="#000" stop-opacity="0.2"/>
          </linearGradient>
          <!-- the inside of the jar: the liquid and the fruit are cut by it -->
          <clipPath id="bl-inside">
            <path d="M32.6 23 L87.4 23 L82.2 113 Q60 117.5 37.8 113 Z"/>
          </clipPath>
          <!-- the four kinds of fruit, drawn once -->
          <g id="bl-straw">
            <path d="M0 -7.6 C5.4 -7.6 7.4 -3.6 6.2 0.6 C5 4.8 2 7.6 0 8.4 C-2 7.6 -5 4.8 -6.2 0.6 C-7.4 -3.6 -5.4 -7.6 0 -7.6 Z" style="fill:var(--berry)"/>
            <path d="M0 -5.2 C3.2 -5.2 4.4 -2.6 3.6 0.4 C2.8 3.2 1.2 5 0 5.6 C-1.2 5 -2.8 3.2 -3.6 0.4 C-4.4 -2.6 -3.2 -5.2 0 -5.2 Z" style="fill:var(--berry-flesh)"/>
            <path d="M0 -4.4 L0 4.4" stroke="#fff" stroke-opacity="0.55" stroke-width="0.9" stroke-linecap="round"/>
            <g fill="#ffe7a8">
              <ellipse cx="-4.6" cy="-2.2" rx="0.45" ry="0.7"/><ellipse cx="4.6" cy="-2.2" rx="0.45" ry="0.7"/>
              <ellipse cx="-4.4" cy="2" rx="0.45" ry="0.7"/><ellipse cx="4.4" cy="2" rx="0.45" ry="0.7"/>
              <ellipse cx="-2.4" cy="5.2" rx="0.4" ry="0.6"/><ellipse cx="2.4" cy="5.2" rx="0.4" ry="0.6"/>
            </g>
            <path d="M-4.6 -7 C-2.4 -10 -1 -8 0 -9.6 C1 -8 2.4 -10 4.6 -7 C2.6 -6.2 1.4 -7.4 0 -6.4 C-1.4 -7.4 -2.6 -6.2 -4.6 -7 Z" style="fill:var(--leaf)"/>
          </g>
          <g id="bl-banana">
            <circle r="6.4" style="fill:var(--banana-skin)"/>
            <circle r="5.5" style="fill:var(--banana)"/>
            <circle r="2.2" style="fill:var(--banana-core)"/>
            <g style="fill:var(--banana-seed)">
              <circle cx="0" cy="-1.3" r="0.42"/><circle cx="1.15" cy="0.65" r="0.42"/><circle cx="-1.15" cy="0.65" r="0.42"/>
            </g>
            <path d="M-3.6 -3.4 A5 5 0 0 1 2 -4.6" fill="none" stroke="#fff" stroke-opacity="0.6" stroke-width="0.9" stroke-linecap="round"/>
          </g>
          <g id="bl-kiwi">
            <circle r="6.6" style="fill:var(--kiwi-skin)"/>
            <circle r="5.7" style="fill:var(--kiwi)"/>
            <circle r="3.6" style="fill:var(--kiwi-light)" fill-opacity="0.5"/>
            <ellipse rx="1.7" ry="1.4" style="fill:var(--kiwi-core)"/>
            <g fill="#1d2414">
              <ellipse cx="0" cy="-2.9" rx="0.32" ry="0.7"/><ellipse cx="2.05" cy="-2.05" rx="0.32" ry="0.7" transform="rotate(45 2.05 -2.05)"/>
              <ellipse cx="2.9" cy="0" rx="0.7" ry="0.32"/><ellipse cx="2.05" cy="2.05" rx="0.32" ry="0.7" transform="rotate(-45 2.05 2.05)"/>
              <ellipse cx="0" cy="2.9" rx="0.32" ry="0.7"/><ellipse cx="-2.05" cy="2.05" rx="0.32" ry="0.7" transform="rotate(45 -2.05 2.05)"/>
              <ellipse cx="-2.9" cy="0" rx="0.7" ry="0.32"/><ellipse cx="-2.05" cy="-2.05" rx="0.32" ry="0.7" transform="rotate(-45 -2.05 -2.05)"/>
            </g>
          </g>
          <g id="bl-blue">
            <circle r="3.4" style="fill:var(--blueberry)"/>
            <circle cx="-1.1" cy="-1.1" r="1.2" fill="#fff" fill-opacity="0.28"/>
            <path d="M-1 -2.6 L0 -1.8 L1 -2.6 L0.6 -3.3 L-0.6 -3.3 Z" fill="#1c2050"/>
          </g>
        </defs>

        <!-- the shadow on the pill's foot line -->
        <ellipse cx="60" cy="168.6" rx="40" ry="2.6" fill="#3a1424" fill-opacity="0.18"/>

        <!-- the power cord, out of the back of the base and down to the floor -->
        <path d="M24 158 C14 158 10 162 8 168 C7 170.4 4 171 1 171" fill="none" stroke="#2a1f26" stroke-width="2.2" stroke-linecap="round"/>
        <path d="M23 157.2 C15 157.4 11.6 160.6 9.8 165" fill="none" stroke="#fff" stroke-opacity="0.25" stroke-width="0.7" stroke-linecap="round"/>
        <g data-body>
          <!-- the motor base, cream enamel -->
          <path d="M30 122 L90 122 Q93 122 93.6 125 L99 162 Q99.6 167 94.6 167 L25.4 167 Q20.4 167 21 162 L26.4 125 Q27 122 30 122 Z" fill="url(#bl-enamel)"/>
          <path d="M30 122 L90 122 Q93 122 93.6 125 L94 128 L26 128 L26.4 125 Q27 122 30 122 Z" fill="#fff" fill-opacity="0.35"/>
          <path d="M21.6 159 L98.4 159 L99 162 Q99.6 167 94.6 167 L25.4 167 Q20.4 167 21 162 Z" fill="#000" fill-opacity="0.1"/>
          <!-- the chrome band with the name -->
          <rect x="27" y="130" width="66" height="7" rx="2" fill="url(#bl-chrome)"/>
          <text class="bl__brand" x="60" y="135.3" text-anchor="middle" data-brand-text>WHIRL</text>
          <!-- the vent slots on the side and the two screws of the band -->
          <g stroke="#000" stroke-opacity="0.16" stroke-width="1.1" stroke-linecap="round">
            <path d="M28.6 142 L27.6 150"/><path d="M31.6 142 L30.8 150"/><path d="M34.6 142 L34 150"/>
            <path d="M91.4 142 L92.4 150"/><path d="M88.4 142 L89.2 150"/><path d="M85.4 142 L86 150"/>
          </g>
          <g>
            <circle cx="30.6" cy="133.5" r="1.7" fill="url(#bl-dial)" stroke="#7d8a92" stroke-width="0.4"/>
            <path d="M29.6 132.7 L31.6 134.3" stroke="#5d6870" stroke-width="0.5" stroke-linecap="round"/>
            <circle cx="89.4" cy="133.5" r="1.7" fill="url(#bl-dial)" stroke="#7d8a92" stroke-width="0.4"/>
            <path d="M88.4 134.3 L90.4 132.7" stroke="#5d6870" stroke-width="0.5" stroke-linecap="round"/>
          </g>
          <!-- a small scratch in the enamel -->
          <path d="M80.6 156 l4.2 -1.4" stroke="#fff" stroke-opacity="0.55" stroke-width="0.5" stroke-linecap="round"/>
          <!-- the speed dial with its ticks -->
          <g fill="none" style="stroke:var(--enamel-ink)" stroke-width="1" stroke-linecap="round" stroke-opacity="0.55">
            <path d="M48.6 152.8 L46.8 154.2"/><path d="M49.2 144.6 L47.2 143.6"/><path d="M54.4 139.6 L53.8 137.6"/>
            <path d="M65.6 139.6 L66.2 137.6"/><path d="M70.8 144.6 L72.8 143.6"/><path d="M71.4 152.8 L73.2 154.2"/>
          </g>
          <circle cx="60" cy="149" r="10.2" fill="#000" fill-opacity="0.12"/>
          <circle cx="60" cy="148.4" r="9.6" fill="url(#bl-dial)"/>
          <circle cx="60" cy="148.4" r="6.6" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="0.8"/>
          <g data-dial>
            <rect x="58.9" y="139.6" width="2.2" height="6.6" rx="1.1" style="fill:var(--berry-deep)"/>
          </g>
          <!-- the rubber feet -->
          <rect x="26" y="164" width="12" height="5" rx="2.2" fill="#2a1f26"/>
          <rect x="82" y="164" width="12" height="5" rx="2.2" fill="#2a1f26"/>

          <!-- the jar, which shakes a little more than the base when the motor runs -->
          <g data-jar>
            <!-- the collar that locks the jar on the base -->
            <path d="M34 112 L86 112 L88.6 122.4 L31.4 122.4 Z" fill="url(#bl-rubber)"/>
            <path d="M35 113.6 L85 113.6" stroke="#fff" stroke-opacity="0.18" stroke-width="1"/>
            <!-- the handle -->
            <path d="M86.4 34 C101 34 104.6 40 104.6 52 L104.6 82 C104.6 94 100 99 84.2 99" fill="none" style="stroke:var(--handle)" stroke-width="7" stroke-linecap="round"/>
            <path d="M88 37.4 C98 37.6 100.8 42 100.8 52 L100.8 82" fill="none" stroke="#fff" stroke-opacity="0.3" stroke-width="1.6" stroke-linecap="round"/>

            <!-- what is inside, cut by the glass -->
            <g clip-path="url(#bl-inside)">
              <rect x="30" y="20" width="60" height="100" fill="#eaf6fa" fill-opacity="0.55"/>
              <g data-bubbles></g>
              <path data-liquid d="M30 92 L90 92 L90 120 L30 120 Z" style="fill:var(--milk)"/>
              <path data-liquid-shade d="M30 92 L90 92 L90 120 L30 120 Z" fill="url(#bl-liquid-shade)"/>
              <path data-surface d="M30 92 L90 92" fill="none" stroke="#fff" stroke-opacity="0.55" stroke-width="1.1"/>
              <g data-foam opacity="0"></g>
              <g data-bits></g>
              <g data-fruit></g>
              <!-- the blade, seen from the side: it turns by squeezing -->
              <g transform="translate(60 109.6)">
                <rect x="-1.4" y="-1" width="2.8" height="5" rx="0.6" fill="#7a868e"/>
                <g data-blade>
                  <path d="M-12 -1.2 C-6 -3.4 6 -3.4 12 -1.2 C6 0.4 -6 0.4 -12 -1.2 Z" fill="url(#bl-chrome)"/>
                </g>
                <circle r="1.6" fill="#5d6870"/>
              </g>
            </g>

            <!-- the glass in front: its edge, its marks and the light on it -->
            <path d="M31 21 L89 21 L83.6 112.6 Q60 117.6 36.4 112.6 Z" fill="url(#bl-glass)" stroke="#9fbfcc" stroke-opacity="0.9" stroke-width="1.6" stroke-linejoin="round"/>
            <g stroke="#7fa3b2" stroke-opacity="0.7" stroke-width="1" stroke-linecap="round">
              <path d="M78.6 46 L84 46"/><path d="M80.2 58 L83.4 58"/><path d="M78.4 70 L82.8 70"/><path d="M80 82 L82.2 82"/><path d="M77.8 94 L81.6 94"/>
            </g>
            <!-- the paper label of the juice bar, stuck on the glass a little crooked -->
            <g transform="rotate(-3 54 32)">
              <rect x="42" y="26" width="25" height="12" rx="1.6" fill="#fffaf2" stroke="#e7d6bd" stroke-width="0.5"/>
              <rect x="42" y="26" width="25" height="2.4" rx="1" style="fill:var(--pill)"/>
              <text class="bl__label" x="54.5" y="33.6" text-anchor="middle">PULP</text>
              <text class="bl__label-small" x="54.5" y="36.6" text-anchor="middle">juice bar</text>
              <path d="M64.4 37.4 L67 34.8 L67 37.4 Z" fill="#000" fill-opacity="0.08"/>
            </g>
            <!-- a few drops of condensation on the cold glass -->
            <g fill="#fff" fill-opacity="0.7">
              <ellipse cx="74" cy="42" rx="0.7" ry="1"/><ellipse cx="76.4" cy="50" rx="0.5" ry="0.75"/>
              <ellipse cx="44" cy="44" rx="0.55" ry="0.8"/><ellipse cx="71" cy="31" rx="0.5" ry="0.7"/>
            </g>
            <g data-splash></g>
            <path d="M37.6 28 L36.8 54" stroke="#fff" stroke-opacity="0.85" stroke-width="2.6" stroke-linecap="round"/>
            <path d="M38.6 62 L38.2 70" stroke="#fff" stroke-opacity="0.7" stroke-width="2" stroke-linecap="round"/>
            <path d="M84 30 L82.6 60" stroke="#fff" stroke-opacity="0.4" stroke-width="1.2" stroke-linecap="round"/>

            <!-- the lid and its cap -->
            <g data-lid>
              <path d="M28.6 13.6 Q28.6 11 31.2 11 L88.8 11 Q91.4 11 91.4 13.6 L91.4 20.4 Q91.4 22.6 89.2 22.6 L30.8 22.6 Q28.6 22.6 28.6 20.4 Z" fill="url(#bl-rubber)"/>
              <path d="M31 13.4 L89 13.4" stroke="#fff" stroke-opacity="0.22" stroke-width="1.2" stroke-linecap="round"/>
              <g data-cap>
                <path d="M51 11.4 L51 6.6 Q51 4.4 53.2 4.4 L66.8 4.4 Q69 4.4 69 6.6 L69 11.4 Z" style="fill:var(--berry)"/>
                <path d="M53 6.4 L67 6.4" stroke="#fff" stroke-opacity="0.4" stroke-width="1.1" stroke-linecap="round"/>
              </g>
            </g>
          </g>
        </g>
      </svg>

      <p class="sr" aria-live="polite" data-live></p>
    </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.

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 blender button” from Animorio and fit it into my site

What it does

On a white background a raspberry pill with the words Blend my smoothie in cream and, under them, Berry & banana in mango yellow. In its round left end stands a blender drawn in SVG: the cream enamel motor base with the light on its side, a chrome band with the name WHIRL held by two screws, vent slots on the sides, the chrome speed dial with its ticks and a raspberry hand, rubber feet; above it the rubber collar, the glass jar with its handle, the measuring marks, a paper label stuck on a little crooked, a few drops of condensation and the reflections, the rubber lid with a raspberry cap. Inside, on a splash of milk, a pile of halved strawberries with seeds and leaves, banana slices, kiwi slices with their seeds and blueberries. Under the mouse the motor gives a little burst: the jar shudders and the fruit hops. On a click the dial snaps all the way round, the motor starts and shakes the jar, the blade spins, the milk hollows into a vortex that climbs the walls, redrawn on every frame; the fruit is dragged round, shrinks and is chopped into flecks of its colours, bubbles rise, pink splashes run down the glass, the milk turns pink and rises to the top while a mango band fills the pill and the words turn to Blending… with a sheen. At the end the motor stops and the dial comes back: the smoothie sloshes from wall to wall and calms down, froth forms on top, the cap jumps and drops back, and the words become Smoothie's ready with a drawn tick. A second click empties the jar and drops fresh fruit back in, bouncing on the pile.

How it works

The blender in SVG, in layers

A single SVG over the left end of the pill, which takes no pointer. Inside the jar everything is cut by a clipPath shaped like the inside: first the back of the glass, then the liquid, the froth, the flecks, the fruit and the blade; in front the glass with its edges, marks, label and reflections, so the contents look behind the glass.

The surface redrawn on every frame

The liquid is a path of 19 points from wall to wall: its height is the level, minus a Gaussian dip in the middle that is the vortex (deeper the faster the motor runs), plus the liquid climbing the walls, plus a standing wave for the slosh. The colour goes from milk to pink with gsap.utils.interpolate, read from the :root variables.

The fruit going round

Each piece has its spot in the pile and an orbit in the vortex, at its own height and radius: the motor's pull takes it from the pile to the orbit, where it turns on an ellipse seen from the side and shrinks at the back. Each piece has the moment the blade gets it: from then it shrinks and fades, while flecks of its colour are born, go round and melt into the pink.

The slosh as a damped spring

When the motor stops the script kicks the wave's amplitude, which on every frame is multiplied by an exponential that damps it: the smoothie goes from wall to wall less and less, like a spring. The shudder of the base and the jar is made of sines at different frequencies, scaled by the motor speed.

The choreography and a real button

A GSAP timeline turns the dial, starts the motor, chops, colours and raises the level for BLEND_TIME seconds, fills the timer, stops the motor, sloshes, adds the froth and makes the cap jump. The drawing loop sits on the GSAP ticker and switches off when nothing moves. It is a button: Enter and Space work like a click, with aria-busy, aria-disabled and an aria-live region.

Values to change

NameFileDefaultWhat it changes
LABELscript.jsBlend my smoothieThe words at rest.
NOTEscript.jsBerry & bananaThe small words under them.
BUSYscript.jsBlending…The words while the motor runs.
DONEscript.jsSmoothie's readyThe words once it is blended.
BRANDscript.jsWHIRLThe name on the chrome band of the base.
BLEND_TIMEscript.js2.8How long the motor runs, in seconds.
SWIRLscript.js1How deep the vortex gets: 0.4 gentle, 1.6 wild.
SLOSHscript.js1How hard the smoothie sloshes when the motor stops.
FRUITscript.js11How many pieces of fruit go in the jar.
SPEEDscript.js1How fast the whole thing runs: 0.6 slow, 1.6 quick.

Try them in the Playground

When to use it

For the order button of a juice bar, a breakfast bar, a delivery app or a kitchen appliance shop, or for a button that starts a job that takes a few seconds.

On phones

The component is 318 pixels wide or the screen minus 32, and 172 tall with a 60 pixel pill; at rest the small words sit under the big ones, so both fit. With no hover, the finger resting on it gives the motor its little burst like the mouse.

With reduced motion

No shudder, no vortex, no fruit going round and no splashes: on a click the fruit fades, the milk turns pink and rises in a fade, the pill fills and the words change by fading.

Pitfalls

  • Contents drawn in front of the glass looked stuck on the outside: they must be cut by a clipPath shaped like the inside, and the glass drawn after them with its reflections.
  • In SVG an attribute like fill="var(--x)" does not read CSS variables: it needs style="fill:var(--x)".
  • The liquid's colour changes over time, so it cannot live only in the CSS: the script reads the :root colours at start and mixes them with gsap.utils.interpolate.
  • Without svgOrigin the dial and the cap turned around the SVG's corner: each part has its pivot in the SVG's coordinates.
  • A drawing loop always running costs even at rest: the ticker is removed when the motor, the slosh and the burst are still.

Libraries

GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.

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

Testing

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