Skip to content
animorio
IT
All components

The Ferris wheel carousel

Carousels, Physics, SVGGSAP, CSS onlyAdvancedCompressed weight: Demo 14 KBGSAP 28 KBFreeDownload the zip

A travel desk’s weekends in the cabins of a steel Ferris wheel: the wheel is dragged round its hub, runs on and snaps on the cabin at the bottom, which opens a little to show its dates and price. Every cabin hangs from its pin like a real pendulum: it lags when the wheel speeds up, swings past when it brakes, and settles.

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 Ferris wheel carousel</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/figtree@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the weekends of a travel desk, each in a cabin of a Ferris wheel. The wheel turns with the arrows, the keys,
         the mouse or a finger, runs on and snaps on the cabin at the bottom, which opens a little to show the dates.
         Every cabin hangs from its pin like a real pendulum: it lags when the wheel speeds up, swings past when it
         brakes and settles. The wheel and the cabins are drawn by the script -->
    <section class="ride" role="region" aria-roledescription="carousel" aria-labelledby="ride-title" data-ride>
      <h2 class="ride__title" id="ride-title">Autumn weekends away</h2>

      <div class="ride__view" tabindex="0" aria-describedby="ride-help" data-view>
        <svg class="ride__wheel" aria-hidden="true" data-wheel></svg>
        <div class="ride__cabins" data-cabins></div>

        <button class="turn turn--prev" type="button" aria-label="Previous weekend" data-prev>
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <g transform="matrix(-1 0 0 1 24 0)"><path d="M5.4 9.6A7 7 0 1 1 18.1 15.5"/><path d="M21.7 13.8 18.1 15.5 17.7 11.5"/></g>
          </svg>
        </button>
        <button class="turn turn--next" type="button" aria-label="Next weekend" data-next data-autoclic>
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M5.4 9.6A7 7 0 1 1 18.1 15.5"/><path d="M21.7 13.8 18.1 15.5 17.7 11.5"/>
          </svg>
        </button>
      </div>
      <p class="sr" id="ride-help">Drag the wheel to turn it, or use the arrow keys. The cabin at the bottom is the weekend chosen.</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.

51 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 Ferris wheel carousel” from Animorio and fit it into my site

What it does

On a black background, alone in the middle, the title Autumn weekends away and under it a Ferris wheel in pale steel: two rims joined by a zigzag truss, spokes laced like a bicycle’s, six main spokes running to the pins, a hub with its bolts and two A-frame legs going down to a boarding deck. From each pin hangs a coloured cabin with a steel canopy, a landscape drawn in its window (dunes at sunset with a tent, a lighthouse on a cliff, a lake among pines, a hut under a glacier, a harbour at night with the moon, a rowboat on a lagoon) and its name below. The cabin at the bottom, Dune Camp, is open over the deck: bigger, with a bigger name and a line with its dates (Oct 16 – 18) and price (€540). On the two sides of the deck, two round buttons with a curved arrow. Pressing the right arrow the wheel turns by a sixth: the cabins lag behind when it sets off, swing past when it brakes and settle slowly, the ones at the top more slowly than those at the bottom; Dune Camp closes and Lighthouse Coast, now at the bottom, opens with a little bounce. The wheel is dragged with the mouse or a finger round its hub; thrown, it runs on, slows down and at the end snaps like a magnet on the nearest cabin, and the cabins swing hard. With the mouse passing over a cabin, the cabin brightens, the landscape comes closer and it rocks a little the way the mouse went; a click on a cabin brings it down the shortest way round, a click on the open one rocks it. With the wheel focused the arrow keys turn it, Home and End go to the first and the last, and a pale ring goes round the open cabin.

How it works

One number for the wheel

The wheel is a single number, p: the cabin at the bottom as a fraction. Cabin i’s pin sits at the angle b = (i − p)·2π/N from the bottom, that is at centre + R·(sin b, cos b). A spring takes p to the chosen cabin, a = ω²·(goal − p) − 2ζω·v, with ω and ζ from GLIDE (ζ under 1, so it goes a touch past); the stiffness is divided by the square root of the distance, so half a turn starts with the same push as one step.

The pins’ acceleration

From p, its speed and its acceleration (the one the spring works out, smooth, not the difference between two frames) comes each pin’s acceleration in pixels: the tangential one, R·b″ along the wheel, and the one towards the hub, R·b′². It is multiplied by PUSH and held under two g with a hyperbolic tangent, so a hard throw does not make the cabins loop the loop.

A pendulum on a moving pin

Every cabin is a pendulum: L·φ″ = −g·sin φ − ax·cos φ + ay·sin φ, plus the air’s damping (HOLD) and a touch of dry friction in the pin, which stops the last small swings instead of letting them fade for ever. g comes from SWING: g = (2π / SWING)²·L. L runs from the pin to the middle of the cabin and grows when the cabin opens; at the top the pull towards the hub takes a little gravity away and the cabins swing more slowly, like real ones. It is integrated every frame with a step of 1/30 s at most, cut into slices of 4 ms.

Inertia and the magnet

With a finger or the mouse what counts is the pointer’s angle round the hub (the change of atan2 is (y·dx − x·dy) / r², with r never under half the radius, where the angle gets shaky), and the wheel chases it on a stiff spring. Let go at more than SNAP cabins a second it runs free and slows down with a = −FRICTION·v; under SNAP the magnet picks the cabin where it would have stopped, p + v / FRICTION, and gets there on the spring: the final snap sets the cabins swinging.

The cabin that opens

Each cabin has its own number f, from 0 closed to 1 open, on a spring that bounces just a little; the CSS works out the width, the height, the window, the name and the line with dates and price from it with calc() and the --f variable. Only the cabin at the bottom opens, and only when the wheel has nearly stopped, so the cabins passing by do not swell.

Drawing, sizes and stillness

The wheel is an SVG redrawn by the script when the width changes: spokes laced in two families, the truss, the pin brackets, a hub with a radial gradient, the A-frame legs; the wheel’s group turns with a single transform attribute, while the light on the rim stays put like the lamp. The radius shrinks so the cabins at the sides fit; with six cabins two neighbours never touch in any position. When everything is still the loop switches off.

Values to change

NameFileDefaultWhat it changes
SWINGscript.js1.25The seconds a cabin takes to swing there and back.
HOLDscript.js0.14How much the air holds a swinging cabin back: 0 it hardly stops (only the pin’s friction is left).
PUSHscript.js0.38How much the cabins feel the wheel speeding up and braking: 0 not at all.
GLIDEscript.js[4.6, 0.62]The wheel’s spring towards a cabin: how quickly it gets there and how much it is held back (under 1 it goes a little past).
FRICTIONscript.js1.5How quickly a thrown wheel slows down before it snaps on a cabin.
SNAPscript.js1.1The speed, in cabins a second, under which a running wheel snaps on the nearest cabin.
WEEKENDSscript.js[['Dune Camp', 'Oct 16 – 18', '€540', '#b9781f', 'dunes'], …]The weekends: name, dates, price from, the cabin’s colour, the landscape in its window (dunes, lighthouse, lake, glacier, port, lagoon). Made for six cabins: with more the wheel packs them tighter and two neighbours may brush.
--wheelstyle.css128pxThe wheel’s radius at most (on a phone it shrinks by itself).
--open-wstyle.css128pxThe width of the open cabin (106 on a phone).

Try them in the Playground

When to use it

For a travel agency’s destinations or experiences, a festival’s weekends, a venue’s nights, a hotel’s rooms, a school’s trips: wherever each thing has a picture and a name, there are only a few (six or seven) and the play of the wheel makes people want to turn it.

On phones

The wheel shrinks to a 128 pixel radius and the cabins to 64 by 84 (106 by 140 the open one), so the cabins at the sides stay on the screen with room to swing. A finger drags the wheel sideways round its hub and throws it (up and down scrolls the page), a tap on a cabin brings it down and the buttons are 42 pixels.

With reduced motion

The wheel does not turn and the cabins do not swing: at each change the wheel fades out and back in with the new cabin already at the bottom and already open, the cabins upright. Dragging is a swipe: one cabin the way the hand went. The mouse over a cabin only brightens it. The component comes in with a fade only.

Pitfalls

  • With pure physics a step of a sixth of a turn gave accelerations of nearly two g and the cabins swung by 50 degrees, knocking into their neighbours: the push is scaled (PUSH) and held under two g with a hyperbolic tangent.
  • With eight cabins, at some angles, the cabin at the side and the one above overlapped: with six, the distance between two neighbouring pins is exactly the radius, and a cabin with its hanger fits in every position.
  • The air’s damping alone makes small swings fade exponentially, for ever: the loop never switched off. A touch of dry friction in the pin (a hyperbolic tangent of the speed) stops them in a finite time, as it really happens.
  • A cabin swelling as the wheel went by looked like a bug: it opens only when the wheel has nearly stopped on it.
  • The name at 12 pixels in a 76 pixel cabin wrapped inside the word (Lighthous / e): the cabin is 80, the name 11.5 and the word no longer breaks.
  • Colours given as var() in the SVG’s presentation attributes are not reliable: the script reads the :root variables and writes them into the wheel’s paths when it redraws it.
  • Near the hub the pointer’s angle jumps for a few pixels: in the angle’s sum the distance from the hub never goes under half the radius.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Figtree (font variabile) 5.3.0.

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

Testing

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