Skip to content
animorio
IT
Back to the library

Bars that fold into figures

Scroll, TextGSAP, CSS onlyAdvancedCompressed weight: Demo 7.3 KBGSAP + ScrollTrigger 45 KBFreeDownload the zip

Scroll inside the preview

A studio’s year in three huge figures, one at a time: each figure arrives as a thin red bar that grows to its measure, breaks into one piece per digit, and each piece rises up its digit like a blind, drawing it. Minimal style.

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>Bars that fold into figures</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/inter-tight@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- the year of a made-up architecture studio in three figures, one at a time. Each figure arrives as a thin bar that
       grows along a line to its measure, breaks into one piece per digit, and every piece rises up its digit like a
       blind, drawing it; then the digits gain weight and the label slides out beside them -->
  <main>
    <section class="apertura">
      <p class="piccolo">Norrow, annual report</p>
      <h1 class="titolo">Our year,<br>measured.</h1>
      <p class="piccolo piccolo--tenue">Three figures. Scroll.</p>
    </section>

    <section class="racconto" data-racconto>
      <div class="palco">
        <header class="testa">
          <p class="marchio">Norrow</p>
          <p class="piccolo piccolo--tenue">Annual report 2025</p>
        </header>

        <div class="scena" data-scena>
          <div class="cifre">
            <figure class="cifra" data-cifra>
              <p class="cifra__numero" data-numero>148</p>
              <figcaption class="cifra__dida">
                <span class="cifra__riga"><span class="cifra__nome" data-scivola>Projects delivered</span></span>
                <span class="cifra__riga"><span class="cifra__nota" data-scivola>Up from 112 the year before</span></span>
              </figcaption>
            </figure>
            <figure class="cifra" data-cifra>
              <p class="cifra__numero" data-numero>23</p>
              <figcaption class="cifra__dida">
                <span class="cifra__riga"><span class="cifra__nome" data-scivola>Countries we built in</span></span>
                <span class="cifra__riga"><span class="cifra__nota" data-scivola>Six of them for the first time</span></span>
              </figcaption>
            </figure>
            <figure class="cifra" data-cifra>
              <p class="cifra__numero" data-numero>96%</p>
              <figcaption class="cifra__dida">
                <span class="cifra__riga"><span class="cifra__nome" data-scivola>Clients who came back</span></span>
                <span class="cifra__riga"><span class="cifra__nota" data-scivola>And most of them brought a friend</span></span>
              </figcaption>
            </figure>
          </div>

          <div class="linea" data-linea aria-hidden="true">
            <i class="tacca" data-tacca></i><i class="tacca" data-tacca></i><i class="tacca" data-tacca></i><i class="tacca" data-tacca></i><i class="tacca" data-tacca></i>
          </div>
          <div class="pezzi" data-pezzi aria-hidden="true"></div>
        </div>

        <nav class="voci" aria-label="The three figures">
          <span class="voce" data-voce>Projects</span>
          <span class="voce" data-voce>Countries</span>
          <span class="voce" data-voce>Clients</span>
        </nav>
      </div>
    </section>

    <section class="apertura apertura--fine">
      <h2 class="titolo titolo--medio">Thank you for<br>building with us.</h2>
    </section>
  </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="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 “Bars that fold into figures” from Animorio and fit it into my site

What it does

The year of a made-up architecture studio, Norrow, on a warm white with black ink and a single red. The screen stops and first a hairline draws itself from one side to the other, with five ticks dropping in one after the other. Then, for every figure, a two-pixel red bar grows along the hairline to its measure, with the count running at its tip. Once there, the bar breaks into as many pieces as there are digits, and a red tick stays on the hairline where it reached. The pieces fly to the foot of their digits, shrinking to their width, one after the other, and rise: below each piece the digit appears, thin as the bar was, like a blind going up. At the top the piece goes a little past, comes back down onto the digit and closes into nothing. Then the digits gain weight with an elastic bounce and the label slides out from behind the figure. Before the next figure the label slides back in and the digits drop behind the hairline. At the bottom the names of the three figures light up in turn. Everything follows the scroll, forwards and backwards.

How it works

The digits and their copy

The script splits each figure into one element per digit and makes an invisible copy of it, always thin (weight THIN), in the same place. Measures are taken on the copy, so they do not change while the real digits gain weight. At the end of the copy there is a zero-height element sitting on the baseline: its edge gives the height of the hairline.

Ink from a canvas

The box of a digit is wider than its drawing. With measureText on a canvas, set in the same font, you read where the ink of each digit starts and ends and how high it rises above the baseline: each piece of bar shrinks exactly to the width of its digit and stops exactly at its top.

Numbers instead of properties

The timeline, tied to the scroll with scrub SCRUB, does not move elements: it moves numbers from 0 to 1 (the bar growing, breaking, and for each digit the flight, the rise, the closing, the drop), all with fromTo. On every change a function redraws the figure from those numbers: position and scaleX of the pieces, the text of the count, the cut of the digits.

The blind

Each digit has a polygon clip-path whose top edge follows the top edge of its piece of bar: below the piece the digit shows, above it does not. Before starting, the cut sits under the lowest ink, so the bellies of the 3 and the 8 do not peek out; the curve of the rise is RISE, a back.out that takes the piece a little past the top before it settles.

The weight and the label

When the pieces have closed, a CSS variable --w goes from THIN to BOLD with elastic.out and is the font-weight of the figure (Inter Tight is a variable font). The label sits beside the figure with left: 100%, so it follows it as it widens, and comes out of an overflow hidden window with xPercent from -104 to 0. On a phone it goes under the hairline.

Values to change

NameFileDefaultWhat it changes
MEASURESscript.js[0.74, 0.46, 0.96]How far the bar of each figure grows, from 0 to 1 of the hairline (one per figure).
THINscript.js200The weight of the digits while the bar draws them.
BOLDscript.js640The weight they settle on in the end.
LINEscript.js2How thick the bar is, in pixels.
RISEscript.js'back.out(1.3)'The curve of each piece rising up its digit: the number in brackets is how far it goes past the top.
STAGGERscript.js0.3The delay between one digit and the next, in steps of the story.
SCRUBscript.js0.6How many seconds the drawing takes to catch up with the scroll.

Try them in the Playground

When to use it

For the figures of an annual report, a yearly review, the about page of a studio or a company: a few important numbers, told one at a time. It works with short figures (up to four or five characters); with more than three figures the section must be made longer.

On phones

The same, with the figure larger in proportion to the screen and the label under the hairline instead of beside it.

With reduced motion

No bars and no movement: the figures are already full with their label and, as you scroll, they take turns with a cross-fade; the names at the bottom light up in turn.

Pitfalls

  • With invalidateOnRefresh and to() tweens, a timeline refreshed halfway down starts again from the values it finds, not from zero: the digits stayed bold from the start. Here every tween is a fromTo, and those that pick up a property already moved have immediateRender: false.
  • The clip-path at the bottom cannot stop at the baseline: the 3, the 8 and the percent sign dip a few pixels under the line and peeked out like a smudge. The starting cut sits under the lowest ink plus a margin.
  • A label in position absolute with left: 100% inside an element as wide as the figure has zero room and shrinks to nothing; with its lines in overflow hidden inside a grid it vanishes completely. It needs width: max-content.
  • Measures must be taken on a thin copy that never moves: taken on the real figure, after a resize halfway through, they would give the widths of the bold digits and the pieces would no longer go back to the right feet.

Libraries

GSAP 3.15.0, ScrollTrigger (GSAP) 3.15.0, Inter Tight (font variabile) 5.3.0.

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

Testing

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