Skip to content
animorio
IT
Back to the library

The ribbon with one side

3D, Text, WebGLWebGL, GSAP, CanvasAdvancedCompressed weight: Demo 8.7 KBGSAP 28 KBThree.js 174 KBFreeDownload the zip

A paper ribbon closed with half a twist, a Möbius strip, in the minimal style: the word «INSIDE» runs slowly round it and after one lap comes back in front on the other face, where it reads «OUTSIDE» in the accent colour. Moving the pointer across it (or the wheel, or a finger) makes the ribbon run faster the way the hand goes, on a spring; let go and it slows down, goes past its pace by a whisker and settles.

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 ribbon with one side</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
  <script type="importmap">
    { "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.178.0/build/three.module.min.js"
    } }
  </script>
</head>
<body>
  <!-- a thin paper ribbon closed with half a twist, a Moebius strip: it has one side and one edge. A word printed on
       it runs round and round; after one lap it comes back on the other face, where a second word waits in the
       accent. The script draws the ribbon and the letters on the canvas -->
  <main class="stage" data-stage>
    <canvas class="canvas" data-canvas aria-hidden="true"></canvas>
    <h1 class="sr" data-sr>Inside, outside: a ribbon with one side.</h1>
    <p class="hint" data-hint>Move across the ribbon</p>
  </main>

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

14 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 ribbon with one side” from Animorio and fit it into my site

What it does

On warm white paper a thin ribbon draws itself: the paper appears along the ribbon starting from the left, goes round once and reveals one face, goes round again and reveals the other, while the ring turns by a quarter and lands with a little bounce. The ribbon has half a twist: at the front it stands up, at the sides it leans, at the back it lies flat. The edge is a single line of ink that goes round twice before it closes, and along the edge run the ticks of a tape measure. Then the letters of «INSIDE» land one after the other: they drop a little, take weight and settle. The ribbon starts to run slowly to the left; the word goes round the back and comes in front again on the other face of the paper, where «OUTSIDE» waits in vermilion, upside down when it is at the back. Moving the pointer over the page makes the ribbon speed up the way the hand goes (backwards too), the letters get heavier with the speed and the ring leans; let go, the speed goes back to its pace on a spring, passes it just a little and stops, and the ring straightens up with a last sway. The pointer high or low shows the ring a little more from above or a little more from the front.

How it works

A ribbon that goes round twice

The line along the middle of the ribbon is a circle on the floor; across it the ribbon turns by half a turn while it goes round once: the direction of the width is cos(u/2) upwards plus sin(u/2) outwards. After one lap the same paper is upside down and faces in. The mesh runs along the ribbon twice (u from 0 to 4π): the first lap is one face of the paper, the second the other, each drawn from its own side only (FrontSide). So every point is drawn once per face, and the same formula gives the normals for the light too.

The letters on a canvas

One canvas holds both words: the first in the red channel, the second in the green one, on the same stretch of paper. The canvas covers only the stretch where the word sits (a copy of half a lap), so every pixel goes to the letters; on the ribbon the paper is not square, and the letters are drawn squeezing the x axis by the right factor. Every letter has its own state (how visible, how heavy, how far above its place) and is drawn on its own at its weight of the variable typeface Archivo; the positions are measured on the whole word, to keep the kerning.

The shader: the print that slides

In the fragment, T = u − flow says which point of the print is there: as the flow grows, the print slides along the ribbon without touching the geometry. The lap (T modulo 4π) picks the channel and the colour: ink on the first face, accent on the other. The canvas is read with textureGrad and the derivatives of T, which do not jump where one copy ends and the next begins. The ticks and the line of the edge are drawn in the shader with fwidth: they stay one pixel wide, and when the ribbon turns edge-on the edge becomes a thin line by itself.

The speed on a spring

The speed of the hand (how many page widths the pointer covers in a second, smoothed and fading by itself) is added to the pace of the ribbon; the real speed chases it on a damped spring, integrated in steps of 1/240 of a second. BOUNCE sets the damping: at 0 it stops without passing its pace, at 1 it passes it and comes back. The ring leans as much as the speed is away from its pace, on a lightly damped spring that makes it sway as it straightens; the weight of the letters grows with the same distance, and the canvas is redrawn only when the weight changes by at least 4.

The opening and the timing

A GSAP timeline reveals the paper along two laps (a uniform that cuts the ribbon where u passes the value), turns the ring by a quarter with a back.out and grows it from 94% with a light elastic. The letters arrive with three tweens staggered by 0.07 s: the fade, the drop with a back.out and the weight from 200 to the chosen one. Then the pace rises from zero with a power2.inOut. The loop of every frame sits in the GSAP ticker, so the bench can slow it down and stop it.

Values to change

NameFileDefaultWhat it changes
FRONT_WORDscript.js'INSIDE'The word on the right side of the ribbon, in ink.
BACK_WORDscript.js'OUTSIDE'The word that comes round on the other face, in the accent colour.
LAPscript.js12How many seconds a word takes to go once round the ribbon, at rest.
PUSHscript.js1.6How much the pointer, the wheel and a finger make the ribbon run: 0 not at all.
BOUNCEscript.js0.4How far the speed goes past its pace when you let go: 0 not at all, 1 a lot.
WEIGHTscript.js480The weight of the letters at rest, from 200 to 800; running they get heavier.
BANDscript.js0.44The width of the ribbon, as a share of the radius of the ring.
TILTscript.js24How much from above you look at the ring, in degrees.

Try them in the Playground

When to use it

For the opening of a portfolio, a studio or a brand that plays on two opposite words (inside and outside, before and after, day and night): the ribbon holds them together on a single side. Also for the page of an event or an exhibition with a title of one or two words, or as an object to look at halfway down a long page.

On phones

The same ring, sized to fit nine tenths of the width; a finger dragged across makes the ribbon run like the pointer, and letting go it settles. The hint at the bottom says «Drag» instead of «Move».

With reduced motion

Nothing turns and nothing slides: the ribbon fades in already drawn, with «INSIDE» in front and «OUTSIDE» at the back. Every four seconds it fades out and comes back showing the other face in front.

Pitfalls

  • A Möbius strip has one side only: drawn with one lap of mesh and DoubleSide, the letters on the reverse came out mirrored. The mesh goes round twice, once per face, each visible only from its own side: on the reverse the writing reads the right way.
  • Letters drawn on a transparent canvas, uploaded without premultiplying, have a full colour with a small alpha at their edges: taking the mask from the red channel, a halo came out around every letter. The canvas has a solid black background and the two words add up with 'lighter', one per channel.
  • With fract() the coordinate of the print jumps where one copy ends and the next begins: the GPU picks the smallest mipmap and a grey line runs across the ribbon. It is read with textureGrad and the derivatives of the continuous coordinate.
  • With a single copy of the word per lap, half of the time the ribbon was blank at the back and the ring looked empty: with two copies there is always one word in front and one at the back, and the two faces take turns.
  • The quarter turn of the opening, put on the same number the pointer spring chases, fought with the spring and left a final push: it is a number of its own, added to the rotation.

Libraries

Three.js 0.178.0, GSAP 3.15.0, Archivo (font variabile, assi wdth e wght) 5.3.0.

Compressed with gzip, the three demo files weigh 8.7 KB; the libraries: GSAP 28 KB, Three.js 174 KB.

Testing

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