Skip to content
animorio
IT
Back to the library

The section index with crosshair circles that regroup on scroll

Scroll, TransitionsGSAP, CSS onlyMediumCompressed weight: Demo 7.5 KBGSAP + ScrollTrigger 45 KBLenis 5.3 KBFreeDownload the zip

Scroll inside the preview

A minimal-style scroll animation on a silver background: two thin rings, each with two crosshairs on its rim, change place, size and turn at every section. Between one section and the next they do not jump: first they regroup on the same circle in the middle, then they spread into the new layout. On the left a thin index with the names of the sections and a red line that fills as you scroll.

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>Raggio</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/hanken-grotesk@5.3.0/wght.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- a made-up maker of carbon wheels, Raggio. The stage sticks while you scroll: two thin rings, each with two
       crosshairs on its rim, change place, size and turn at every stop. Between one stop and the next they first
       regroup on the same circle in the middle, then spread to the new layout. On the left a thin index names the
       sections and a line fills with the scroll -->
  <main>
    <section class="corsa" data-corsa>
      <div class="palco" data-palco>
        <div class="anelli" data-anelli aria-hidden="true">
          <div class="anello" data-anello>
            <span class="anello__tacca" data-tacca></span>
            <span class="mirino mirino--a" data-mirino><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>
            <span class="mirino mirino--b" data-mirino><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>
          </div>
          <div class="anello" data-anello>
            <span class="anello__tacca" data-tacca></span>
            <span class="mirino mirino--a" data-mirino><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>
            <span class="mirino mirino--b" data-mirino><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>
          </div>
        </div>

        <span class="angolo angolo--ns" aria-hidden="true"><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>
        <span class="angolo angolo--ne" aria-hidden="true"><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>
        <span class="angolo angolo--ss" aria-hidden="true"><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>
        <span class="angolo angolo--se" aria-hidden="true"><svg viewBox="0 0 14 14"><path d="M7 0v14M0 7h14"/></svg></span>

        <a class="marchio" href="#" data-marchio>Raggio</a>

        <nav class="indice" aria-label="Sections" data-indice>
          <span class="indice__binario"><span class="indice__pieno" data-pieno></span></span>
          <ul>
            <li><a href="#" data-voce="1">Philosophy</a></li>
            <li><a href="#" data-voce="2">Craft</a></li>
            <li><a href="#" data-voce="3">Innovation</a></li>
            <li><a href="#" data-voce="4">Ride</a></li>
          </ul>
        </nav>

        <div class="titoli" data-titoli>
          <div class="titolo" data-titolo>
            <p class="titolo__nota">Carbon wheels</p>
            <h2>Built around a circle</h2>
          </div>
          <div class="titolo" data-titolo>
            <p class="titolo__nota">Philosophy</p>
            <h2>Light is a decision</h2>
          </div>
          <div class="titolo" data-titolo>
            <p class="titolo__nota">Craft</p>
            <h2>Laid by hand, in carbon</h2>
          </div>
          <div class="titolo" data-titolo>
            <p class="titolo__nota">Innovation</p>
            <h2>Stiffer where it matters</h2>
          </div>
          <div class="titolo" data-titolo>
            <p class="titolo__nota">Ride</p>
            <h2>Made for the long climb</h2>
          </div>
        </div>

        <p class="cenno" data-cenno><span>Scroll</span><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 3v13M4.5 10.5 10 16l5.5-5.5"/></svg></p>
      </div>
    </section>

    <footer class="piede">
      <p>Raggio wheels are laid by hand, one rim at a time.</p>
    </footer>
  </main>

  <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="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.

32 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 section index with crosshair circles that regroup on scroll” from Animorio and fit it into my site

What it does

This is the page of Raggio, a made-up maker of carbon wheels. The stage stays still while you scroll and goes through five stops: the opening, Philosophy, Craft, Innovation and Ride. On the silver background, lighter in the middle, there are only two thin rings in a light grey, each carrying two cross-shaped crosshairs on its rim, at the two ends of a diameter. At the opening the two rings sit low, with their centre below the bottom of the page, like a horizon; in the next stops they stand side by side, swap places turning half a turn, shrink or widen, until at the end one sits inside the other, moved to the left. When you go from one stop to the next the rings do not jump: first they both regroup on the same circle in the middle, then they spread into the new layout, the second a beat after the first, with a curve that starts quickly and lands slowly. The crosshairs turn with their ring, so halfway through a turn they become an X; when they arrive they settle, turning back and dropping by a pixel. A small red tick runs along each rim, faster while you scroll. In the middle a short headline changes at every stop, its words rising one after the other; on the left a thin index names the sections, the current one darkens and a red line fills with the scroll. Pointing at a name leans the rings towards it; a click takes you to that stop. In the corners of the stage four small crosses work as registration marks.

How it works

Layouts in screen units

Every stop is a pair of poses, one per ring: offset from the middle, diameter, turn and whether the crosshairs show. They are written in units of a 1080-high screen, as the original site does with its lvh measures: diameters of 900, 840, 754, 700, 678, side offsets of 234, 330, 346 (the last stop here at 300, to keep the index clear). The script multiplies them by the smaller of height / 1080 and width / 900, so on a phone the width decides and the rings never get too big.

Regroup before changing

A ScrollTrigger on the section reads the progress and works out the current stop and whether you are close to a change (ZONE, a share of a stop before and after). A small state machine does the rest: if the stop shown is not the right one, or you are close to a change, the rings first go to the shared circle in the middle, 840 across, in GATHER seconds; then, away from the change, they spread into the new stop in MOVE seconds. Even scrolling fast they always pass through the middle.

The site's curve

Every move uses cubic-bezier(.33, 0, 0, 1), the curve of the original site: it starts quickly and lands slowly. GSAP has no CSS curves, so it is solved by hand with a few Newton steps and passed as an ease function. The second ring starts LAG seconds after the first. Width and height are animated, not the scale, so the rim stays one pixel at every size.

The crosshairs, the tick and the index

The crosshairs are children of the ring and turn with it; when they arrive GSAP takes them from 0.4 to 1 with back.out and from minus 45 degrees to zero, then down by a pixel and up. The red tick is a box as big as the ring, turned by GSAP's ticker, faster when the scroll has speed. The index line is a scaleY equal to the progress; on a phone the index lies down under the headline and the line becomes a scaleX.

Leaning, clicking, resizing

Pointing at a name (only with hover: hover) turns the group of rings LEAN degrees towards it and shifts it a little; a click takes you with Lenis to the middle of that stop. On every resize the poses in pixels are worked out again and set at once, even in the middle of a move, then the machine starts again.

Values to change

NameFileDefaultWhat it changes
RIDEscript.js1How much scroll the five stops take: 2 is twice as long.
GATHERscript.js0.8The seconds the rings take to regroup in the middle.
MOVEscript.js1.2The seconds the rings take to spread into the next layout.
LAGscript.js0.08The seconds between the first and the second ring.
ZONEscript.js0.14How long the rings stay regrouped around a change, as a share of a stop.
SIZEscript.js1How big the rings are: 1 is the measure of the layouts.
LEANscript.js7How many degrees the rings lean towards the name you point at.
TICKscript.js1How fast the red tick runs along the rims.

Try them in the Playground

When to use it

As the opening of a technical product page (wheels, bicycles, watches, instruments) split into chapters; as the index of a presentation or a company story in a few sections; wherever a precise, calm movement, like a technical drawing, is wanted behind short headlines.

On phones

The same animation: the layouts are sized on the width, so the rings stay in the stage and whatever goes past the sides is cut by the stage. The index lies down at the bottom, under the headline, and its line fills from left to right; a tap on a name takes you to the stop.

With reduced motion

The rings do not travel: at every stop they fade out, change place and fade back in. The tick stays still, the headlines fade in, no smooth scrolling.

Pitfalls

  • With scale instead of width and height the one-pixel rim gets thicker and thinner as the ring grows: width and height are animated, which for just two elements costs little.
  • Scrolling fast the stop changes while the rings are still travelling: retargeting halfway makes them skip the middle. The state machine lets every leg finish and then looks at where the scroll has got to.
  • The crosshairs are crosses drawn with vector-effect: non-scaling-stroke: when they shrink on the way out the line stays one pixel and does not vanish halfway.
  • The poses are worked out in pixels: on resize (the demo window changes height too) they are redone at once, otherwise the rings keep the old size until the next change.

Libraries

GSAP 3.15.0, ScrollTrigger 3.15.0, Lenis 1.3.26, Hanken Grotesk (font variabile) 5.3.0.

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

Testing

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