Skip to content
animorio
IT
Back to the library

The through-the-O transition

Transitions, TextGSAP, CSS only, CanvasMediumCompressed weight: Demo 7.8 KBGSAP 28 KBFreeDownload the zip

A page change in the minimal style: the page is one huge word and its O is vermilion. On a click the counter of the O opens like a lens with the next page already inside it, tiny, word and all; then the view flies into the O, which grows and slides to the middle until the hole is the whole screen.

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 through-the-O transition</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>
    <!-- the first page: one huge word, and the next page waits inside its O -->
    <section class="page page--a is-on" data-page="a" aria-label="Shop">
      <header class="bar">
        <span class="brand">Orra</span>
        <nav class="nav" aria-label="Pages">
          <a class="w" href="#shop" aria-current="page"><span class="w__t">Shop</span></a>
          <a class="w" href="#frames" data-go="b"><span class="w__t">Frames</span></a>
        </nav>
      </header>
      <h1 class="big" data-big aria-label="Look">L<span class="o" data-o>o<i class="o__base"></i></span>ok</h1>
      <div class="below" data-late>
        <p class="lede">Hand-made frames and ground lenses, from one small workshop.</p>
        <a class="go w" href="#frames" data-go="b" data-autoclic>
          <span class="w__t">See the frames</span>
          <svg class="go__arrow" viewBox="0 0 26 12" aria-hidden="true"><path d="M1 6h22M18.5 1.5 23.5 6l-5 4.5"/></svg>
        </a>
      </div>
      <footer class="foot" data-late>
        <span>Eyewear and lenses</span>
        <span>Since 1968</span>
      </footer>
    </section>

    <!-- the second page: dark, with its own O to go back through -->
    <section class="page page--b" data-page="b" aria-label="Frames" aria-hidden="true">
      <header class="bar">
        <span class="brand">Orra</span>
        <nav class="nav" aria-label="Pages">
          <a class="w" href="#shop" data-go="a"><span class="w__t">Shop</span></a>
          <a class="w" href="#frames" aria-current="page"><span class="w__t">Frames</span></a>
        </nav>
      </header>
      <h2 class="big" data-big aria-label="Round">R<span class="o" data-o>o<i class="o__base"></i></span>und</h2>
      <ul class="list" aria-label="Round frames">
        <li class="row" data-late><span class="row__rule" data-rule></span><span class="row__name">Round 46</span><span class="row__kind">Tortoise acetate</span><span class="row__size">46 mm</span></li>
        <li class="row" data-late><span class="row__rule" data-rule></span><span class="row__name">Round 49</span><span class="row__kind">Black acetate</span><span class="row__size">49 mm</span></li>
        <li class="row" data-late><span class="row__rule" data-rule></span><span class="row__name">Round 52</span><span class="row__kind">Crystal</span><span class="row__size">52 mm</span></li>
      </ul>
      <div class="below below--b" data-late>
        <a class="go go--back w" href="#shop" data-go="a" data-autoclic>
          <svg class="go__arrow" viewBox="0 0 26 12" aria-hidden="true"><path d="M25 6H3M7.5 1.5 2.5 6l5 4.5"/></svg>
          <span class="w__t">Back to the shop</span>
        </a>
      </div>
    </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.

38 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 through-the-O transition” from Animorio and fit it into my site

What it does

The site of a small made-up eyewear workshop, Orra, in two pages: the first paper-coloured with the word Look huge across it, the second nearly black with the word Round; on both the O is vermilion. On load the letters of Look rise one after another, narrow, and widen on the width axis with a spring; then the line of text and the link arrive. A click on See the frames sends the text and the footer down a few pixels and away, and the word is left alone. The counter of the O, the hole inside the letter, opens from the middle like a lens: inside it is the dark page, tiny, with the word Round across the lens. The page takes half a step back, then the view flies into the O: the paper page grows around the counter, faster and faster, carrying it to the middle of the screen; the page inside the lens grows with it and reaches its own size just before the hole covers the corners too. Once there the letters of Round take a breath on the width axis starting from the O, the rules of the list of frames draw themselves and the link back rises. A click on Back to the shop goes back through the O of Round, with the paper page tiny inside the lens.

How it works

The counter read from the ink

The hole in the letter is not guessed: the script draws an o on a canvas in the same typeface (Archivo, weight 700, expanded width), starts from the middle of the letter and walks right, left, up and down until it meets ink. Then it walks along the diagonal to see how square the counter is and turns it into a superellipse, |x/a|^n + |y/b|^n = 1. The measures are kept in ems, so they hold for any size of the word; the real place of the O on the page comes from an empty box resting on its baseline.

The lens in pixels

The new page sits on top of the old one, cut by a 96-point clip-path polygon that draws the superellipse. The clip-path is applied before the transform, so the lens is written in the small page's own pixels: radii divided by the page's scale. While it is small, a layer of its colour as big as the screen sits behind it (a ::before with a negative inset, not a box-shadow, which left a light seam on the edge).

The flight

The old page grows with its transform-origin at the top left: the scale is exponential (exp(ln(most) · z)), so the flight feels steady and power3.in makes it speed up. Meanwhile the centre of the counter slides to the centre of the screen. The largest scale is the one at which the ellipse inside the counter covers the corners: the square root of (W/2)²/a² + (H/2)²/b². The page inside the lens grows with the same scale times a factor that at first fits its word in the lens (FIT), and reaches 1 before the end; the point at the centre of the lens moves from its word to the middle of the page.

The word takes a breath

Each letter is an inline-block whose font-stretch is tied to the --w variable. On arrival the word starts at 108 and springs back to 125 with elastic.out, staggered from the O out to both sides; on load the letters start at 62, Archivo's narrowest width, and rise from below one after another.

Resizing mid-flight, phone and reduced motion

The flight is a state made of numbers (how open the lens is, how far the zoom has gone) and every frame redraws it with the measures of the moment: if the window changes halfway, the word is measured again, the counter is worked out again and the flight carries on right. On a phone it is the same transition with the word fitted to the width. With reduced motion the two pages swap with a fade.

Values to change

NameFileDefaultWhat it changes
OPENscript.js0.6The seconds the lens takes to open inside the O.
ZOOMscript.js1.15The seconds of the flight through the O.
PULLscript.js0.02How far the page steps back before the flight: 0 = it goes at once.
FITscript.js0.86How wide the next page's word is inside the lens, against the hole: 1 = edge to edge.
SPEEDscript.js1The speed of everything: 2 is twice as fast, 0.5 half as fast.

Try them in the Playground

When to use it

To go from the home page to a section on a site where a single word is the cover: an eyewear brand, an optician, a photo studio, an agency with an O in its name, the opening page of a collection. It works with any word that has an o, a d, a p or a q: just move the baseline box to the right letter.

On phones

The same transition: the word shrinks to fit the width of the screen and the lens is smaller, but the next page fits inside it with its word; the flight takes the counter to the middle of the tall screen and widens it to the corners.

With reduced motion

Nothing flies: the next page fades in over the first one, with its word and its list already in place.

Pitfalls

  • clip-path is applied before transform: written in screen pixels, the lens came out shrunk along with the small page. Its centre and radii are written in the page's own coordinates, divided by its scale.
  • An ellipse inside Archivo's squarish counter left four paper-coloured wedges in the corners: the counter is a superellipse, and its exponent is measured along the drawing's diagonal.
  • With a box-shadow to spread the small page's colour a light seam stayed on its edge, running like a rule under the word; a ::before layer larger than the page, behind it, leaves none.
  • When the dark page stepped back the stage's paper background showed behind it, like a frame: the stage takes the colour of the page that is leaving.
  • Resizing while the word was taking its breath (letters at --w 108) measured the word narrower and it then ran off the screen: the letters are put back to 125 while they are measured.

Libraries

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

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

Testing

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