Skip to content
animorio
IT
Back to the library

The tunnel that makes room

WebGL, Physics, HoverWebGL, GSAPAdvancedCompressed weight: Demo 9.0 KBGSAP 28 KBThree.js + RoomEnvironment 176 KBFreeDownload the zip

Gold rings, gold tubes and pearls float without weight in a thick ring around the middle of the screen: a tunnel seen from inside, with the name in the empty middle. The pointer pushes them away and they turn as they move; a click opens the wall out to the edges and lets it come back. At the start they rush in from outside while the view widens.

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 tunnel that makes room</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/cormorant@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/cormorant@5.3.0/wght-italic.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/manrope@5.3.0/index.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",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/"
      }
    }
  </script>
</head>
<body>
  <!-- the tunnel: a WebGL canvas behind everything -->
  <canvas class="stage" data-stage aria-hidden="true"></canvas>

  <header class="bar">
    <a class="brand" href="#">Hoop House</a>
    <nav class="bar__nav" aria-label="Sections">
      <a href="#">Rings</a>
      <a href="#">Hoops</a>
      <a href="#">Pearls</a>
      <a href="#">Visit</a>
    </nav>
  </header>

  <!-- what sits in the empty middle of the tunnel -->
  <main class="hole">
    <svg class="mark" viewBox="0 0 64 64" aria-hidden="true">
      <circle cx="32" cy="32" r="22" fill="none" stroke="currentColor" stroke-width="5"/>
      <circle cx="32" cy="32" r="13" fill="none" stroke="currentColor" stroke-width="1.6" opacity="0.6"/>
    </svg>
    <h1 class="title">Fine jewellery, <em>weightless.</em></h1>
    <a class="shop" href="#">Shop the hoops</a>
  </main>

  <p class="hint" data-hint>Move the pointer to make room. Click to scatter everything.</p>

  <!-- where the poster and the collaudo put the pointer: on the tunnel wall, right of the middle -->
  <span class="aim" data-aim aria-hidden="true"></span>

  <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.

30 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 tunnel that makes room” from Animorio and fit it into my site

What it does

The home page of an invented jeweller, Hoop House. Over a forest green background, lighter in the middle, 120 objects float: gold rings, hollow gold tubes like beads, and pearls, in three golds, polished or brushed. They sit in a thick ring around the middle, which stays empty: there sit a small ring mark, the headline “Fine jewellery, weightless.” and a button. At the top the name and four links, at the bottom a hint. On arrival the objects rush in from outside the screen while the view widens; then they turn slowly and sway. The pointer pushes them away, the harder the further it gets in: they move off turning, knock into each other and come back when the pointer leaves. A click opens the wall out to the edges of the window and closes it again in a couple of seconds. If nobody moves the mouse for two and a half seconds, an invisible pointer goes round the wall.

How it works

The scene

Three.js draws on a transparent canvas over the CSS background. The reflections in the gold come from a room of lights (RoomEnvironment) run through the PMREMGenerator, with ACES tone mapping. The lens sits 18 units from the middle and the objects float around depth −5; its angle starts at 18 degrees and every frame covers 12% of the way to 40, so the view widens while the objects arrive.

The objects

Three shapes: a torus (radius 1, thickness 0.3), a hollow bead made by turning a rectangle with round corners (LatheGeometry) and a sphere. Every shape with its finish is an InstancedMesh: polished gold (roughness 0.16), brushed gold (0.42), and pearl with clearcoat, sheen and a touch of iridescence. Each one’s colour is setColorAt. The positions come from a seeded random, so the tunnel is always built the same: a random angle, a radius between 15 and 25 units (off screen), depth −5 give or take 1.

The physics

Sixty steps a second, at a fixed rate. Every force is an impulse divided by the mass, with the original’s numbers: the masses come from the volume of its colliders (4.8 s³ for rings, 2.4 s³ for tubes). Outside the ring, objects come back in with 2 · (d − R); inside, after a second and a half, they go out with 5 · (0.9R − d) and, below the radius of the hole, with 15 · (R − d). The pointer pushes with PUSH · (REACH + size − distance). Every step the speed is divided by 1 + step · DAMPING and never goes past 30 units a second.

The knocks

Every object is a ball or a capsule, that is a segment with a radius: for two objects the script finds the closest points of their segments (Ericson’s method). If they are closer than the sum of the radii they move apart, each in proportion to the other’s mass, and the part of the speed that brought them together is cancelled with a bounce of 0.15. An off-centre knock adds a spin. Along the axis there is a cylinder: no object gets closer to it than the hole.

The pointer and the click

The pointer on the screen becomes a point at the depth of the objects through a Raycaster, worked out again at every step with the lens of that moment. A finger counts while it stays down. The click is a GSAP tween: in 0.35 seconds a value goes from 0 to 1 and in 1.4 it goes back to 0; meanwhile the radius of the ring heads for SCATTER and whatever is inside gets pushed out with (R − d) · value · 30. With no pointer for WANDER seconds, an invisible one goes round the wall.

Values to change

NameFileDefaultWhat it changes
COUNTscript.js120How many objects float on a wide screen; on a narrow one six in ten.
MIXscript.js[0.45, 0.3, 0.25]The shares of rings, tubes and pearls.
HOLEscript.js3.4The radius of the empty middle, in scene units (the window is about 17 high).
PUSHscript.js180How hard the pointer pushes: the impulse for every unit it gets inside an object.
REACHscript.js3.5How far around the pointer it pushes, in units, plus the object’s own size.
SCATTERscript.js10How far a click opens the wall, as a radius: 10 is the edge of the window.
SPINscript.js0.35How fast the objects turn on their own at most, in radians per second.
DAMPINGscript.js0.95How quickly movement dies down: at 0.95 about six tenths of the speed go every second.
WANDERscript.js2.5The seconds without a pointer before an invisible one goes round the wall (0 for never).
GOLDscript.js['#f2d08c', '#dcae55', '#b9862f']The golds, one picked at random for every ring and tube.
PEARLscript.js'#f5efe4'The pearls’ colour.
FOVscript.js[18, 40]The lens angle at the start and once everything has arrived, in degrees.

Try them in the Playground

When to use it

For an opening that should feel like a shop window rather than a page: jewellery, perfume, sweets, toys, materials. It wants objects you recognise from afar and a few words in the empty middle; for a page that must load in a flash or for a long text it is too much.

On phones

On a narrow screen there are six objects in ten and they are smaller, and the resolution goes up to 1.75 at most. A finger resting on the screen pushes like the pointer while it stays down, a tap opens the wall; when the finger lifts, after two and a half seconds the invisible pointer goes round.

With reduced motion

No rushing in and no motion: the tunnel is settled before it is drawn, seven seconds of physics done in an instant, and it holds still. The pointer and the click do nothing, and the hint at the bottom says so.

Pitfalls

  • The depth needs a real spring. With the original’s weak pull the knocks pushed objects all the way behind the lens, and up close they covered the middle: here the spring has its own brake and knocks push mostly sideways (the depth of the knock direction weighs 0.35).
  • Pearls are small: with the mass of their volume they flew off like bullets. They get a higher density, 18 s³.
  • The original’s click, a push of 120 and a radius of 40, is made to leave the scene: on a page that stays it empties the screen for seconds. Here the wall opens up to the edge (radius 10, push 30).
  • The pointer’s point must be worked out again at every step with the lens of that moment: at the start the angle goes from 18 to 40 degrees and the same point of the screen lands somewhere else.

Libraries

Three.js 0.178.0, GSAP 3.15.0, Cormorant (font variabile) 5.3.0, Manrope (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 9.0 KB; the libraries: GSAP 28 KB, Three.js + RoomEnvironment 176 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