Skip to content
animorio
IT
Back to the library

The car in the calendar

Scroll, SVGGSAP, CSS onlyEasyCompressed weight: Demo 5.4 KBGSAP + ScrollTrigger 45 KBFreeDownload the zip

Scroll inside the preview

A season’s calendar, one race per row. When the list reaches the middle of the screen a car seen from above drives in and stays there while the rows slide under it, and the row passing under it lights up. At the end it drives off, and if you scroll back it comes in from below, turned round.

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 car in the calendar</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/big-shoulders-display@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/barlow@5.3.0/400.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/barlow@5.3.0/600.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header class="bar">
    <a class="brand" href="#">Brightwater Racing</a>
    <nav class="bar__nav" aria-label="Sections">
      <a href="#">Team</a>
      <a href="#">Calendar</a>
      <a href="#">Tickets</a>
    </nav>
  </header>

  <section class="intro">
    <p class="intro__kicker">Car 11 · Coastal GT Cup</p>
    <h1 class="intro__title">Nine weekends, one car, the whole coast.</h1>
    <p class="intro__hint">Scroll down the calendar, then back up.</p>
  </section>

  <!-- the calendar: the car waits above the screen, drives in when the list reaches the middle and leaves at its end -->
  <section class="calendar" data-calendar>
    <div class="calendar__sticky" aria-hidden="true">
      <div class="drift" data-drift>
        <svg class="car" data-car viewBox="0 0 200 420">
          <defs>
            <radialGradient id="shadow" cx="0.5" cy="0.5" r="0.5">
              <stop offset="0.55" stop-color="#000" stop-opacity="0.55"/>
              <stop offset="1" stop-color="#000" stop-opacity="0"/>
            </radialGradient>
            <linearGradient id="paint" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" stop-color="#d9dde4"/>
              <stop offset="0.18" stop-color="#ffffff"/>
              <stop offset="0.82" stop-color="#ffffff"/>
              <stop offset="1" stop-color="#d9dde4"/>
            </linearGradient>
            <linearGradient id="glass" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" stop-color="#2a3140"/>
              <stop offset="1" stop-color="#0e1118"/>
            </linearGradient>
            <clipPath id="body"><path d="M36 28C60 16 140 16 164 28C176 34 182 48 184 66L188 118C190 140 186 160 184 176L182 246C184 268 190 290 188 316L184 366C182 392 166 410 136 414C112 417 88 417 64 414C34 410 18 392 16 366L12 316C10 290 16 268 18 246L16 176C14 160 10 140 12 118L16 66C18 48 24 34 36 28Z"/></clipPath>
          </defs>
          <ellipse cx="100" cy="216" rx="112" ry="222" fill="url(#shadow)"/>
          <!-- the tyres, peeking out under the arches -->
          <g fill="#151820">
            <rect x="2" y="64" width="26" height="70" rx="7"/><rect x="172" y="64" width="26" height="70" rx="7"/>
            <rect x="2" y="290" width="26" height="70" rx="7"/><rect x="172" y="290" width="26" height="70" rx="7"/>
          </g>
          <!-- the body and its livery -->
          <path d="M36 28C60 16 140 16 164 28C176 34 182 48 184 66L188 118C190 140 186 160 184 176L182 246C184 268 190 290 188 316L184 366C182 392 166 410 136 414C112 417 88 417 64 414C34 410 18 392 16 366L12 316C10 290 16 268 18 246L16 176C14 160 10 140 12 118L16 66C18 48 24 34 36 28Z" fill="url(#paint)"/>
          <g clip-path="url(#body)">
            <rect x="70" y="0" width="22" height="420" fill="#b6ff3b"/>
            <rect x="108" y="0" width="22" height="420" fill="#b6ff3b"/>
            <rect x="94" y="0" width="12" height="420" fill="#0b0e14"/>
            <path d="M0 196H200V214H0Z" fill="#0b0e14" opacity="0.08"/>
          </g>
          <!-- the rear window, the roof with its number, the windscreen -->
          <path d="M50 106C70 98 130 98 150 106L146 146C120 140 80 140 54 146Z" fill="url(#glass)"/>
          <circle cx="100" cy="192" r="30" fill="#ffffff" stroke="#0b0e14" stroke-width="5"/>
          <text x="100" y="206" text-anchor="middle" font-family="'Big Shoulders Display Variable', Impact, sans-serif" font-weight="900" font-size="38" fill="#0b0e14">11</text>
          <path d="M46 262C72 248 128 248 154 262L162 304C126 294 74 294 38 304Z" fill="url(#glass)"/>
          <path d="M60 266C80 258 104 256 122 258" fill="none" stroke="#fff" stroke-opacity="0.35" stroke-width="4" stroke-linecap="round"/>
          <!-- mirrors, headlights, the rear wing -->
          <path d="M18 252L4 246L4 262L18 266Z" fill="#0b0e14"/><path d="M182 252L196 246L196 262L182 266Z" fill="#0b0e14"/>
          <path d="M34 384C42 396 54 404 68 406L64 394C52 392 42 388 34 384Z" fill="#fff6c9"/>
          <path d="M166 384C158 396 146 404 132 406L136 394C148 392 158 388 166 384Z" fill="#fff6c9"/>
          <rect x="0" y="4" width="200" height="22" rx="4" fill="#0b0e14"/>
          <rect x="0" y="2" width="10" height="30" rx="3" fill="#0b0e14"/><rect x="190" y="2" width="10" height="30" rx="3" fill="#0b0e14"/>
        </svg>
      </div>
    </div>

    <h2 class="calendar__title">Calendar 2027</h2>
    <ol class="rounds" data-rounds>
      <li class="round" data-round><span class="round__no">Round 1</span><b class="round__name">Harbour Ring</b><span class="round__date">14–16.03.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Round 2</span><b class="round__name">Saltmarsh Park</b><span class="round__date">04–06.04.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Round 3</span><b class="round__name">Ridgeback Circuit</b><span class="round__date">25–27.04.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Round 4</span><b class="round__name">Lantern Bay Streets</b><span class="round__date">16–18.05.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Round 5</span><b class="round__name">Pine Hollow Raceway</b><span class="round__date">06–08.06.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Round 6</span><b class="round__name">Kestrel Downs</b><span class="round__date">27–29.06.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Round 7</span><b class="round__name">Copperfield Speedway</b><span class="round__date">18–20.07.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Round 8</span><b class="round__name">Blue Mesa International</b><span class="round__date">22–24.08.2027</span><i class="round__line" data-line></i></li>
      <li class="round" data-round><span class="round__no">Finale</span><b class="round__name">Winter Hill</b><span class="round__date">03–05.10.2027</span><i class="round__line" data-line></i></li>
    </ol>
  </section>

  <section class="outro">
    <h2 class="outro__title">See you at the first corner.</h2>
    <a class="outro__link" href="#">Get weekend passes</a>
  </section>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.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.

48 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 car in the calendar” from Animorio and fit it into my site

What it does

The page of an invented racing team, Brightwater Racing, with the 2027 season calendar: nine weekends on invented circuits, one per row, with the round number, the circuit’s name and the dates, divided by a thin line. When the start of the calendar reaches the middle of the screen, a car seen from above drives in from the top, white with two lime green stripes and number 11 on the roof, and stops in the middle of the screen, over the list. The rows slide under it: the one crossing the middle of the screen turns lime green, text and line, and goes back to white as it leaves. While the calendar goes by, the car drifts slowly. When the end of the list passes the middle, the car drives off downwards; scroll back and it comes in from below, nose up, and turns round again as it leaves at the top.

How it works

The frame that holds still

The section’s first child is a sticky frame as tall as the screen, with a negative bottom margin of the same height, so it does not push the list down. The car sits in the middle of the frame. The section has overflow: clip, which cuts the car at the section’s edges without breaking the sticky.

The car

Two ScrollTriggers with callbacks: when the top of the section crosses the line at LINE% of the screen the car drives in (y to 0, IN seconds, expo.out); when the end of the list crosses the same line it drives off downwards (y to one screen, OUT seconds, expo.in) and only when it is gone does it turn 180 degrees. Scrolling back the opposite happens: it comes in from below nose up, and leaves at the top turning back to 0 degrees.

The rows

Every row has its own ScrollTrigger, from when its top crosses the line to when its bottom does. Entering, text and line take the accent colour in ON seconds with power2.out; leaving, they go back to their colour in OFF seconds with power2.inOut.

The drift and the way back

A tween tied to the scroll moves the frame from -DRIFT% to DRIFT% while the section crosses the screen. On every refresh a function looks at where the section and the end of the list are and puts the car straight where it belongs, above, in or below, turned the right way.

Values to change

NameFileDefaultWhat it changes
INscript.js0.8The seconds the car takes to drive in.
OUTscript.js0.8The seconds it takes to drive off.
DRIFTscript.js12How far the car drifts while the list goes by, as a share of its frame (%).
ONscript.js0.2The seconds a row takes to light up.
OFFscript.js0.6The seconds a row takes to go back.
LINEscript.js50Where on the screen the rows light up and the car comes and goes, from the top (%).

Try them in the Playground

When to use it

For a calendar, a programme, a running order, the stages of a journey: any list where something moves forward. In place of the car any thing seen from above will do, a boat, a bike, a train.

On phones

The car is a third of the screen wide and the rows go on two levels: the round above, name and dates below. The finger scrolls like the wheel and the car comes and goes the same way.

With reduced motion

The car does not drive: it fades out and fades back in where it belongs, and the frame does not drift. The rows still light up.

Pitfalls

  • With overflow: hidden the section becomes a scroll container and the sticky frame stops sticking: use overflow: clip, which cuts without creating a scroll.
  • The screen-tall frame must be taken out of the flow with a negative margin equal to its height, or it pushes the list down by a screen.
  • The half turn happens once the car is gone, at the end of the tween: turning it while in view would make it spin on the spot.
  • After a refresh, or opening the page halfway down, the callbacks have not fired yet: the car must be put straight where it belongs by looking at the positions, without waiting for a scroll.

Libraries

GSAP 3.15.0, Big Shoulders Display (font variabile) 5.3.0, Barlow 5.3.0.

Compressed with gzip, the three demo files weigh 5.4 KB; the libraries: GSAP + ScrollTrigger 45 KB.

Testing

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