
The enfilade gallery
Galleries, Transitions, Images, 3D
NewFree
A photographer's colour slides in the round tray of a seventies projector, built in 3D. Forward or back, the tray turns one notch and swings a little on its click-stop, the slide drops into the projector and bounces, and up on the screen the picture rises from below the other way round, because the lens turns everything over; then the autofocus hunts and settles. The beam is full of dust in the colours of the picture, and the captions are written in pen on the mounts.
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 slide projector gallery</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/nothing-you-could-do@5.3.0/index.css">
<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">
<!-- a photographer's slides in the round tray of a seventies projector. The tray clicks round a notch, the slide
drops into the gate and the lamp throws it up on the screen, through a beam full of dust; the autofocus hunts
and settles. The tray, the projector and the remote are built in CSS 3D, the photographs are painted by the
script, the beam and its dust are a canvas -->
<section class="show" aria-roledescription="carousel" aria-label="Colour slides, 1974 to 1979" data-gallery data-autoclic>
<div class="show__fit" data-fit>
<figure class="screen" data-screen>
<div class="screen__roller" aria-hidden="true"></div>
<div class="screen__cloth" aria-hidden="true">
<div class="screen__spill" data-spill></div>
<div class="screen__gate" data-gate>
<canvas class="screen__pic" data-pic></canvas>
<canvas class="screen__pic screen__pic--fade" data-pic-fade></canvas>
<div class="screen__warm"></div>
<div class="screen__vignette"></div>
<div class="screen__hot"></div>
</div>
<div class="screen__weave"></div>
</div>
<div class="screen__batten" aria-hidden="true"><span class="screen__pull"></span></div>
<figcaption class="sr" data-alt></figcaption>
</figure>
<canvas class="beam" aria-hidden="true" data-beam></canvas>
<svg class="cord" aria-hidden="true" focusable="false" data-cord><path data-cord-line/><path data-cord-shine/></svg>
<div class="remote" role="group" aria-label="Projector remote" data-remote>
<div class="remote__body">
<span class="remote__grip" aria-hidden="true"></span>
<div class="remote__keys">
<button class="key key--back" type="button" aria-label="Reverse: previous slide" data-prev>
<span class="key__cap">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12.5 6.5 7 12l5.5 5.5M18 6.5 12.5 12l5.5 5.5"/></svg>
</span>
<span class="key__name">Reverse</span>
</button>
<button class="key key--on" type="button" aria-label="Forward: next slide" data-next>
<span class="key__cap">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M11.5 6.5 17 12l-5.5 5.5M6 6.5 11.5 12 6 17.5"/></svg>
</span>
<span class="key__name">Forward</span>
</button>
</div>
<span class="remote__plate" aria-hidden="true">Autofocus</span>
</div>
</div>
<div class="room" data-room>
<div class="world" data-world></div>
</div>
</div>
<p class="sr" aria-live="polite" data-live></p>
</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>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
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The slide projector gallery” from Animorio and fit it into my siteThe projector is a small world in pixels (x to the right, y down, z towards you) inside a box with perspective, tilted 38 degrees with rotateX. Body, deck and back are flat faces with translate3d and rotateX or rotateY; the tray, the hub and the lens are cylinders made of 44, 16 and 18 strips set in a ring (rotateY or rotateZ, then translateZ by the radius), each lit by the side it faces. The slides are real buttons standing edgeways in the tray: rotateY by the notch and translate3d by the radius, with a face on each side and backface-visibility hidden. So the vanishing point, the order of the faces and the click on a slide are all the browser's work.
The tray's turn is a number in degrees pulled by a spring towards the right notch: the motor keeps it under TURN_SPEED degrees a second while it is more than 6 degrees away, then the click-stop spring takes it and, with the damping DETENT gives, lets it go a little past and come back. The spring is stiff, so it is integrated in fixed steps of 1/240 of a second inside every frame (with long steps it blew up). For a far slide the shorter way round is chosen; by hand, the finger moves the aim and on release its speed decides whether to stop at the nearest slide or the next.
First the slide in the projector rises back into the tray (GSAP, 0.24 s), then the tray turns, then the new one drops under real gravity (2600 pixels a second squared) down to DROP of its height, and bounces, losing three quarters of its speed, until the knock is small. The part that has sunk below the tray's floor is cut with a clip-path on the mount, and an orange light comes up from the slot and lights the film. A pointer over a slide lifts it on a spring; the loop stops when nothing moves any more.
On the screen the picture follows the slide turned over: its offset is (1 − sunk/DROP) times the height of the frame, so it falls when the slide rises and rises, bouncing, when the slide drops. With no slide in, the lamp shows through, dimmed by the shutter. On arrival the frame is blurred by BLUR pixels with a CSS filter and the focus hunts in three beats (sharp, a little past, sharp again) over FOCUS_TIME seconds, the picture breathing by one per cent. Over the picture: a multiplied veil the colour of the lamp (WARMTH), the vignette (VIGNETTE), the hot spot and the weave of the cloth.
The six photographs are painted pixel by pixel on a 660 × 440 canvas with value noise and its octaves: the sky with its clouds lit from above, the tulips one head to a cell of a grid in perspective, the sea with its crests and the sun's glitter, the mesas with their layers, the houses and their reflections in the canal taken from the picture itself and moved by the water, the net of light on the floor of the pool. Then the film pass: fuller colour, an S curve, warm shadows, grain, a lens's softness and a glow round the lights. One picture is painted a frame while the room is still dark, then the lamp comes on.
The beam is a canvas over the screen in screen mode: a soft cone and 96 rays, one for each cell of a 12 × 8 grid of the picture, each fading from the lens towards the screen in the colour of the picture where it lands (moved along with the slide). DUST specks live in the beam's own coordinates (how far from the lens, where on the screen), glint as they turn and are brighter near the lens. They only move while the air is stirred (a change, a bounce, a hand in the beam) and slowly come to rest: at rest nothing is redrawn. The preview's click from a script presses Forward twice, picks a slide from the tray, presses Reverse, and goes back to the first.
| Name | File | Default | What it changes |
|---|---|---|---|
TURN_SPEED | script.js | 330 | How fast the tray turns, in degrees a second. |
DETENT | script.js | 0.45 | How far the click-stop lets the tray swing past its notch before it holds it (0 stops it dead). |
DROP | script.js | 0.42 | How far a slide sinks into the projector, as a share of its height. |
FOCUS_TIME | script.js | 0.95 | The seconds the autofocus takes to hunt and settle. |
BLUR | script.js | 7 | How far out of focus a slide lands, in pixels on the screen. |
DUST | script.js | 150 | The specks of dust floating in the beam. |
VIGNETTE | script.js | 0.55 | How dark the corners of the projected picture go. |
WARMTH | script.js | 0.3 | How much the tungsten lamp warms the colours. |
For a photographer's portfolio or a photo archive, the page of an exhibition or of a photo book, travel memories, a site that tells a place in pictures, a hotel or holiday home showing what lies around it: anywhere the photographs deserve their evening, one at a time, with the lights off.
The screen takes almost the whole width and the projector stands below it, with a longer beam and the remote at the bottom right. You tap the remote, tap a slide in the tray, or drag the screen with a finger (to the left for the next picture) or the tray itself; the dust moves round a finger passing through the beam. The same animations as on a computer, measured for the small screen.
The tray is already turned and the slide already in place: on the screen the pictures swap with a crossfade. No bounces, no focus hunting, no dust moving, no slides lifting; the room fades in.
GSAP 3.15.0, Archivo (font variabile, asse wdth) 5.3.0, Nothing You Could Do (font) 5.3.0.
Compressed with gzip, the three demo files weigh 27 KB; the libraries: GSAP 28 KB.
Tested on 17 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.