Skip to content
animorio
IT
Back to the library

The carousel

SVGGSAP, CSS onlyAdvancedCompressed weight: Demo 9.5 KBGSAP 28 KBFreeDownload the zip

A carousel seen from three quarters, all in SVG: the striped roof with its valance and bulbs, the mirrored centre column and the little horses on twisted poles. It really turns in perspective, the horses rise and fall out of step, then it slows, stops with a sway and starts again. A click changes the speed.

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>La giostra dei cavalli</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="banco" data-banco>
    <header class="testo">
      <p class="marchio">Hollis Bay Pleasure Pier</p>
      <h1 class="titolo">Pick a horse,<br>hold on tight.</h1>
      <p class="frase">The painted gallopers at the end of the pier turn every summer evening, under the lights, until the last ride.</p>
      <button class="velocita" type="button" data-velocita>
        <span class="velocita__fondo" aria-hidden="true"></span>
        <svg class="velocita__ferro" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6.5 20.5C4 17 3.6 11 6 7.2 8 4 11 3 12 3s4 1 6 4.2c2.4 3.8 2 9.8-.5 13.3" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" /><circle cx="6.6" cy="12" r="1.1" fill="currentColor" /><circle cx="17.4" cy="12" r="1.1" fill="currentColor" /><circle cx="8.6" cy="6.6" r="1.1" fill="currentColor" /><circle cx="15.4" cy="6.6" r="1.1" fill="currentColor" /></svg>
        <span class="velocita__scritta">Speed: <span data-nome-velocita>steady</span></span>
      </button>
    </header>

    <div class="palco" data-palco>
      <svg class="scena" data-scena viewBox="0 0 900 820" preserveAspectRatio="xMidYMid meet" role="img" aria-label="A carousel with painted horses seen from three quarters: the platform turns, the horses rise and fall on their twisted poles, then it slows down and stops. Click to change the speed.">
        <defs>
          <linearGradient id="g-specchio" x1="0" y1="0" x2="1" y2="1">
            <stop offset="0" style="stop-color: var(--specchio-chiaro)" />
            <stop offset="1" style="stop-color: var(--specchio)" />
          </linearGradient>
          <linearGradient id="g-ombra-tetto" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" stop-color="#000" stop-opacity="0.38" />
            <stop offset="0.3" stop-color="#000" stop-opacity="0" />
            <stop offset="0.62" stop-color="#000" stop-opacity="0" />
            <stop offset="1" stop-color="#000" stop-opacity="0.42" />
          </linearGradient>
          <linearGradient id="g-colonna" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" style="stop-color: var(--colonna-scura)" />
            <stop offset="0.45" style="stop-color: var(--colonna)" />
            <stop offset="1" style="stop-color: var(--colonna-scura)" />
          </linearGradient>
          <linearGradient id="g-bordo" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" stop-color="#000" stop-opacity="0.4" />
            <stop offset="0.5" stop-color="#000" stop-opacity="0" />
            <stop offset="1" stop-color="#000" stop-opacity="0.4" />
          </linearGradient>
        </defs>

        <ellipse class="ombra" cx="450" cy="752" rx="372" ry="30" />

        <g class="giostra" data-giostra>
          <!-- la piattaforma: il bordo, il pavimento con le assi che girano -->
          <g data-piattaforma>
            <ellipse class="piatto-sotto" cx="450" cy="648" rx="344" ry="90" />
            <rect class="piatto-sotto" x="106" y="618" width="688" height="30" />
            <g data-fascia></g>
            <rect x="106" y="618" width="688" height="30" fill="url(#g-bordo)" />
            <ellipse class="pavimento" cx="450" cy="618" rx="344" ry="90" />
            <g data-assi></g>
            <ellipse class="pavimento-orlo" cx="450" cy="618" rx="334" ry="86" />
          </g>

          <!-- i cavalli dietro la colonna -->
          <g data-dietro></g>

          <!-- la colonna centrale con gli specchi -->
          <g data-colonna>
            <rect x="404" y="236" width="92" height="384" fill="url(#g-colonna)" />
            <g data-specchi></g>
            <rect class="oro" x="398" y="300" width="104" height="10" rx="3" />
            <rect class="oro" x="398" y="548" width="104" height="10" rx="3" />
            <ellipse class="oro-scuro" cx="450" cy="620" rx="62" ry="16" />
            <ellipse class="oro" cx="450" cy="614" rx="56" ry="13" />
          </g>

          <!-- i cavalli davanti alla colonna -->
          <g data-davanti></g>

          <!-- il tetto a strisce, la mantovana e le lampadine -->
          <g data-tetto>
            <path class="tetto" data-falda d="" />
            <g data-strisce></g>
            <path data-ombra-tetto fill="url(#g-ombra-tetto)" d="" />
            <g data-mantovana></g>
            <path class="orlo-tetto" data-orlo d="" />
            <g data-lampadine></g>
            <!-- il pennacchio in cima -->
            <line class="asta" x1="450" y1="74" x2="450" y2="22" />
            <path class="bandiera" data-bandiera d="" />
            <circle class="oro" cx="450" cy="76" r="9" />
            <circle class="oro-chiaro" cx="447" cy="73" r="3" />
          </g>
        </g>
      </svg>
    </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.

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

What it does

On opening the platform widens with a bounce, the roof drops from above and bounces into place, the little horses grow on their poles with an elastic and the valance bulbs light up one after another, then stay on, steady. Then the carousel starts: slowly, it speeds up, turns at full speed and brakes. Everything turns together: the red and cream stripes of the roof, the scallops of the valance, the mirrors of the column (which light up when they face us), the floorboards and the panels of the rim. The horses pass in front of the column and then behind it, where they get smaller; at the sides they are seen foreshortened. Each one rises and falls on its pole, out of step with the others, and pitches a little as it climbs; the twists of the poles slide as if they were turning. The pennant on top flutters harder when the carousel runs and droops when it stops. At the stop the platform swings back a little, then forward, and settles; after a pause it starts again. A click on the carousel or on the button changes the speed: steady, gallop, gentle. Copy for the made-up fair Hollis Bay Pleasure Pier.

How it works

Everything from one angle

The state of the carousel is one number: A, the degrees turned by the platform. One draw function, called by GSAP’s ticker every frame, works out everything from it: the stripes, the scallops, the bulbs, the mirrors, the boards, the panels, the horses, the poles and the pennant. The carousel is seen from three quarters, so every circle is an ellipse: the point at angle g sits at x = centre + r·sin g and y = height + r·0.26·cos g. With a positive cos g it is in front, with a negative cos g it is behind.

Stripes that turn

The roof is a cone: the cream slope is fixed, and on top are the red stripes, each a sector between two angles. Of each sector only the front part is drawn, cut between −90° and 90°: a triangle from the top of the roof to seven points on the rim. The same cut works for the scallops of the valance (with a half-moon lower edge, 11 + 17·sin(π·u)), for the panels on the rim of the platform and for the mirrors of the column. A fixed dark gradient on the sides of the roof does the rest of the roundness.

In front of and behind the column

The column sits between two empty groups: data-dietro before it, data-davanti after it. Every frame the horses are sorted by cos g and each goes into the right group with appendChild, only when needed. The farther they are, the smaller (from 0.78 to 1.04). Each horse is tangent to the circle, so its width is scale(cos g, 1): in front it heads right, behind it heads left, at the sides it is foreshortened (never below 0.2).

Up and down the poles

The rise is tied to the turn, not to time: up = ALTEZZA_SALTO × (0.5 − 0.5·cos(A × SALTI + phase)), with a different phase for each horse. So when the carousel slows down the horses slow down too, and when it stops they stop. The pitch is the sine of the same phase. The twists of the pole are slanted dashes shifted by A / 18: after a whole turn they are back in place, so the repeating loop does not jump.

The timeline and the click

A timeline repeating forever: the run is a tween from 0 to 1 with a hand-made curve (soft ramp, full speed, soft ramp) that lands on a whole number of turns; then the sway, five keyframes of extra degrees (back, forward, back, still), and a pause. The click changes the timeline’s timeScale with a 0.9 s tween, so the carousel speeds up or slows down without jumps, and it jolts the carousel with an elastic. The pennant uses the speed measured between two frames: it flutters and lifts while running, droops when still.

Values to change

NameFileDefaultWhat it changes
DURATA_CORSAscript.js10Seconds of each run, from the start to the stop.
GIRIscript.js3Platform turns in each run.
CAVALLIscript.js6How many horses ride on the platform.
SALTIscript.js4Rises and falls of each horse per turn (a whole number, so the loop comes back the same).
ALTEZZA_SALTOscript.js30How high the horses climb the pole.
DONDOLIOscript.js7Degrees of the sway when the carousel stops.
--rosso, --crema, --orostyle.css#D8443B, #F6E7CF, #E9B949The roof stripes, the valance and the brass.

Try them in the Playground

When to use it

For fairs, funfairs, village fêtes, toy shops, children’s pages, birthday invitations and summer or Christmas pages: a cheerful scene people watch turning for a long time and that invites a touch.

On phones

The fair copy goes on top and the carousel below, smaller but the same: same horses, same turns, same sway. The speed changes by tapping the carousel or the button.

With reduced motion

The carousel is already still in place with its lights on and fades in; the click only changes the speed label, with a fade, with nothing moving.

Pitfalls

  • Integrating speed frame by frame, the loop never comes back the same and the carousel stops at any old angle: better a tween with a curve that says where it lands, on a whole number of turns.
  • Everything that depends on the angle must come back the same after a whole turn (rises with a whole SALTI, twists shifted by A / 18 on a 10-unit step): otherwise the horses and poles jump when the timeline starts over.
  • A sector going from back to front must be cut at −90° and 90° after bringing its centre between −180° and 180°: without it, the scallops across the wrap jump from one side to the other.
  • Moving the horses between the two groups every frame is cheap, but only if done when needed: a constant appendChild makes the page recompute for nothing.
  • A horse at the side has scale(cos g) close to zero and turns into a line: it must be kept at 0.2 at least, with the right sign, so it shows foreshortened and does not vanish.

Libraries

GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.

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

Testing

Tested on 11 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 animations