Skip to content
animorio
IT
All components

The rocket launch button

Buttons, Physics, Transitions, SVGCanvas, GSAPAdvancedCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

The button that sends a campaign: a 3, 2, 1 countdown on a rolling digit, the rocket shaking in its well, ignition with sparks and smoke pouring out from under the pill, then liftoff with a plume that curls in the air until the rocket is gone off the top. It stays Launched with a thin wisp of smoke, and a new rocket rises out of the well.

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 rocket launch button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- the smoke under the button: the exhaust pours out from under the pill as from a launch pad, and the plume -->
  <canvas class="sky sky--back" data-sky="back" aria-hidden="true"></canvas>

  <main class="stage">
    <!-- the launch button: a countdown on a rolling digit, the rocket shakes on its pad, lifts off with a plume of
         smoke and flies out of the page; the button stays Launched with a thin wisp of smoke, then a new rocket rises -->
    <div class="launch" data-launch data-autoclic>
      <button class="launch__btn" type="button" aria-label="Launch campaign to 12,480 subscribers" data-btn>
        <span class="launch__heat" data-heat aria-hidden="true"></span>
        <span class="launch__pad" data-pad aria-hidden="true">
          <svg class="launch__ring" viewBox="0 0 64 64"><circle cx="32" cy="32" r="30" pathLength="1" data-ring/></svg>
        </span>
        <span class="launch__label" aria-hidden="true">
          <span class="launch__line" data-line="idle">Launch campaign</span>
          <span class="launch__line" data-line="count">Launching in<span class="launch__digits"><span class="launch__reel" data-reel></span></span></span>
          <span class="launch__line launch__line--done" data-line="done"><svg class="launch__check" viewBox="0 0 24 24"><path d="M5 12.6l4.3 4.3L19 7.2" pathLength="1" data-check/></svg>Launched</span>
        </span>
      </button>
      <p class="launch__hint"><span data-hint>12,480 subscribers, sent all at once</span></p>
      <p class="sr" aria-live="polite" data-live></p>
    </div>
  </main>

  <!-- the smoke over the button: the vapour before ignition, the puffs at the rim of the well, the thin wisp -->
  <canvas class="sky" data-sky="front" aria-hidden="true"></canvas>

  <!-- the rocket: it sits over the pad, and the clip lets a new one rise out of the pad as from a silo -->
  <svg class="rocket" data-rocket viewBox="-40 -60 80 120" aria-hidden="true">
    <defs>
      <linearGradient id="rk-hull" x1="0" y1="0" x2="1" y2="0">
        <stop offset="0" class="rocket__hull-a"/>
        <stop offset="0.38" class="rocket__hull-b"/>
        <stop offset="0.72" class="rocket__hull-c"/>
        <stop offset="1" class="rocket__hull-d"/>
      </linearGradient>
      <linearGradient id="rk-fin" x1="0" y1="0" x2="1" y2="0">
        <stop offset="0" class="rocket__fin-a"/>
        <stop offset="1" class="rocket__fin-b"/>
      </linearGradient>
      <linearGradient id="rk-metal" x1="0" y1="0" x2="1" y2="0">
        <stop offset="0" stop-color="#5b6070"/>
        <stop offset="0.45" stop-color="#b9bdc8"/>
        <stop offset="1" stop-color="#3d414d"/>
      </linearGradient>
      <linearGradient id="rk-heat" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0.35" class="rocket__heat-a"/>
        <stop offset="1" class="rocket__heat-b"/>
      </linearGradient>
      <radialGradient id="rk-glow">
        <stop offset="0" class="rocket__glow-a"/>
        <stop offset="0.35" class="rocket__glow-m"/>
        <stop offset="1" class="rocket__glow-b"/>
      </radialGradient>
      <linearGradient id="rk-flame" x1="0" y1="0" x2="0" y2="1" gradientUnits="objectBoundingBox">
        <stop offset="0" class="rocket__flame-a"/>
        <stop offset="0.3" class="rocket__flame-b"/>
        <stop offset="1" class="rocket__flame-c"/>
      </linearGradient>
      <linearGradient id="rk-core" x1="0" y1="0" x2="0" y2="1" gradientUnits="objectBoundingBox">
        <stop offset="0" stop-color="#fff"/>
        <stop offset="0.5" class="rocket__core-a"/>
        <stop offset="1" class="rocket__core-b"/>
      </linearGradient>
      <clipPath id="rk-body"><path d="M0 -27C7.4 -21 10.2 -10 10.2 3.5L9.2 15.2Q9 17.4 6.9 17.4H-6.9Q-9 17.4 -9.2 15.2L-10.2 3.5C-10.2 -10 -7.4 -21 0 -27Z"/></clipPath>
      <clipPath id="rk-silo" clipPathUnits="userSpaceOnUse"><path data-silo d="M-600 -6000H600V0H26A26 26 0 0 1 -26 0H-600Z"/></clipPath>
    </defs>
    <!-- the light of the flame round the rocket, and where the flame hits the floor of the well -->
    <circle class="rocket__halo" r="1" data-halo/>
    <ellipse class="rocket__splash" cx="0" cy="25" rx="40" ry="8" data-splash/>
    <g clip-path="url(#rk-silo)">
      <g data-craft>
        <!-- the flame, redrawn every frame under the nozzle: the outer flame, the white core, the bright knots -->
        <g class="rocket__flame" data-flame>
          <path class="rocket__fire" data-fire/>
          <path class="rocket__core" data-core/>
          <path class="rocket__knots" data-knots/>
        </g>
        <path class="rocket__fin" d="M-9.4 1.5C-14.8 5.6 -17.6 11.6 -17.4 20.6L-9 15.6Z"/>
        <path class="rocket__fin" d="M9.4 1.5C14.8 5.6 17.6 11.6 17.4 20.6L9 15.6Z"/>
        <path class="rocket__nozzle" d="M-5.2 17.2H5.2L6.8 23.4Q6.9 24.2 6.1 24.2H-6.1Q-6.9 24.2 -6.8 23.4Z"/>
        <path class="rocket__hull" d="M0 -27C7.4 -21 10.2 -10 10.2 3.5L9.2 15.2Q9 17.4 6.9 17.4H-6.9Q-9 17.4 -9.2 15.2L-10.2 3.5C-10.2 -10 -7.4 -21 0 -27Z"/>
        <g clip-path="url(#rk-body)">
          <rect class="rocket__cap" x="-12" y="-30" width="24" height="15.5"/>
          <rect class="rocket__band" x="-12" y="8.2" width="24" height="2"/>
          <rect class="rocket__burn" x="-12" y="-4" width="24" height="24" data-burn/>
        </g>
        <path class="rocket__keel" d="M-1.3 9.5H1.3V22.2Q1.3 23.4 0 23.4Q-1.3 23.4 -1.3 22.2Z"/>
        <circle class="rocket__rim" cx="0" cy="-4" r="5.2"/>
        <circle class="rocket__glass" cx="0" cy="-4" r="3.6"/>
        <path class="rocket__shine" d="M-2.2 -5.6A2.6 2.6 0 0 1 0.2 -6.9"/>
      </g>
    </g>
    <!-- the sparks of the ignition: short streaks, all in one path -->
    <path class="rocket__sparks" data-sparks/>
  </svg>

  <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.

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

What it does

On a black background a graphite pill button: on the left a round well with a small white and coral rocket in it, on the right the words Launch campaign; below it, lined up with the words and so right of the well, the line 12,480 subscribers, sent all at once. With the mouse over it the rocket lifts a little and a small flame flickers under it. Clicking, the words rise away and Launching in 3 arrives, while the line below says Press again to abort; the 3 rolls into 2 and then 1 like the digits of a counter, a coral ring fills round the well, the well gives a little thump at every number and the rocket shakes more and more; on the 1 it vents a little vapour from its sides. At zero it lights up and the line says Sending to 12,480 inboxes: a white and orange flame comes out of the nozzle, sparks fly and fall back, the button warms with orange light and trembles, and the exhaust smoke pours out from under the pill to both sides, warm near the flame. A moment later the rocket lets go and climbs faster and faster, leaning a little, with a long flame and the bright knots of its exhaust, leaving a plume that swells and curls in the air; it is gone off the top while the words become a tick with Launched and the line below says Sent to 12,480 inboxes. A thin wisp of smoke rises from the empty well. After a moment, or with a click, a new rocket rises from the bottom of the well and the button is back to Launch campaign. Clicking during the countdown aborts the launch and the line says Launch aborted.

How it works

The countdown on the reel

The digits from COUNT to 1 sit in a column inside a window one digit high: at every beat (BEAT seconds) the column moves up one digit with expo.out, like a mechanical counter. Meanwhile an SVG circle with pathLength 1 fills with stroke-dashoffset from 1 to 0, and a shake number that GSAP grows with power1.in sets how hard the rocket shakes.

The rocket over the well

The rocket is an SVG fixed on the page, outside the button, so it can fly away: every frame it puts itself over the well by reading getBoundingClientRect, and follows the button when it shrinks, trembles or the window changes size. The shaking is simplex noise read at high speed, the lean grows with the square of the climb, and a clipPath shaped like the well lets the new rocket rise from its bottom as from a silo.

The flame redrawn

The flame is drawn in the rocket's own frame, under the nozzle: two Bézier curves that meet at the tip, an orange one outside and a white one inside. Its length follows the thrust (from pilot flame to full power) and flickers with two noises at different speeds; on the pad it is cut at the floor of the well and spreads there into a flattened glow. At full thrust the bright knots of the exhaust appear.

The smoke as particles

Each puff is a particle on a canvas as big as the page: it is pushed by the curl of a noise field that drifts in time (the noise's slope turned by 90 degrees), so the smoke swirls on itself without ever bunching up; it rises a little, is slowed by the air, grows and fades as it ages. On the pad the exhaust goes down, hits the floor under the button and spreads to both sides; in flight the rocket leaves a puff every seven pixels of its way.

Two canvases, under and over

The smoke is on two canvases: the one under the button takes the exhaust and the first stretch of the plume, which so pours out from under the pill without covering the words; the one over it takes the vapour, the puffs at the rim of the well and the last thin wisp. The puffs are four ready-made drawings of heaped little clouds, in two versions, grey and warmed by the flame, and the canvas runs at half resolution: smoke is soft, and the graphics card does a quarter of the work.

Values to change

NameFileDefaultWhat it changes
COUNTscript.js3The number the countdown starts from.
BEATscript.js0.8The seconds each number of the countdown lasts.
SHAKEscript.js1How hard the rocket shakes on the pad before it lifts off.
FLIGHTscript.js1.5The seconds the rocket takes to fly out of the page.
LEANscript.js9How many degrees the rocket leans to the right as it climbs.
SMOKEscript.js1How much smoke: 0.5 is half the puffs, 2 twice as many.
SWIRLscript.js1How much the smoke curls and swirls as it rises.
DONEscript.js2.6The seconds the button says Launched before a new rocket rises.

Try them in the Playground

When to use it

For an app's big action, the one that goes and does not come back: sending a campaign or a newsletter, publishing a site, deploying, launching a product. The countdown leaves time to think again and abort. It wants a dark page and room above the button, where the rocket flies.

On phones

The button is 340 pixels wide, or the screen minus 48; the rocket puts itself back over the well every frame, so it also follows the screen turning. A tap does the countdown like a click; the pilot flame of the mouse passing over does not come with a touch. The smoke canvases run at half resolution on dense screens too.

With reduced motion

The countdown stays, with the digits changing without rolling and the ring filling; the rocket does not shake or fly: at zero it fades out of the well and Launched appears with the tick. No smoke, flame or sparks. On the way back the rocket reappears with a fade.

Pitfalls

  • With the first version (full-resolution smoke, mixed additively with 'lighter', and the flame's light as a second pass) a browser without a graphics card froze for tens of seconds: now the canvas runs at half resolution with normal mixing, the warmth is a second set of puffs already tinted, and the flame is an SVG path.
  • GSAP rounds px values to whole pixels: the ring's stroke-dashoffset from 1 to 0 jumped halfway instead of filling. It needs autoRound: false.
  • A single canvas over everything covered the words with the exhaust smoke: there are two canvases, and the exhaust goes on the one under the button, from which it pours out at the sides as from a real launch pad.
  • The rocket cannot live inside the button, or it could not leave it: it is a fixed element of the page put back over the well every frame, so it also follows the press, the trembling and the resizing.
  • A canvas does not read CSS variables: the colours of the smoke and the flame are read with getComputedStyle and redrawn into the puffs at every launch, so changing them live works.
  • The particles' time step is capped at 1/30 of a second: after a tab left in the background, a jump in time would shoot them all away at once.

Libraries

GSAP 3.15.0, Geist (font variabile) 5.3.0.

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

Testing

Tested on 4 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: Done

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