Skip to content
animorio
IT
All components

The slide projector gallery

Galleries, Images, 3D, TransitionsGSAP, CSS only, CanvasAdvancedCompressed weight: Demo 27 KBGSAP 28 KBFreeDownload the zip

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.

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

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 “The slide projector gallery” from Animorio and fit it into my site

What it does

On a black background, the dark room of a slide show. At the top a pull-down screen, with its roller, the bar at the bottom and the ring to pull it by; on the cloth the projected picture, warm with the tungsten lamp, its corners darkening and a hot spot in the middle. At the bottom the projector seen from behind and a little above: the brown body with its satin aluminium deck, the vents at the back with the lamp glowing through them, the socket for the remote, the lens sticking out in front. On top stands the black tray with six slides upright in their slots, each in its own mount (white plastic, card, grey plastic), the film in its window and, on the lamp side, the place and the date written in pen; on the other side the lab's printed line and the month it was developed. The slide in the projector sits lower than the others: it has dropped into the slot, where the orange light comes up, and its caption shows. From the lens the beam rises to the screen, made of rays that each take the colour of the picture where they land, with the dust sparkling in it. Beside it the corded remote: two big keys, Reverse and Forward. Pressing one, the slide rises back into the tray and the picture slides away down the screen, the tray turns one notch with a small swing on its click-stop, the next slide drops into the slot and bounces, and its picture rises up the screen bouncing with it, out of focus; then the focus hunts for it, goes a little past and finds it. The remote's cord sways, the dust starts moving and slowly settles. Over a slide in the tray the pointer lifts it as if to take it; clicked, the tray turns to it the shorter way round, several notches if need be. The tray also turns by hand, dragging the tray or the screen, and let go it stops at the nearest slide, or the next one if it was thrown. Passing the pointer through the beam moves the dust round the hand.

How it works

A projector in CSS 3D

The 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 on its click-stop

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.

The slide that drops

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.

The picture upside down

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.

Photographs painted by the script

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 and the dust

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.

Values to change

NameFileDefaultWhat it changes
TURN_SPEEDscript.js330How fast the tray turns, in degrees a second.
DETENTscript.js0.45How far the click-stop lets the tray swing past its notch before it holds it (0 stops it dead).
DROPscript.js0.42How far a slide sinks into the projector, as a share of its height.
FOCUS_TIMEscript.js0.95The seconds the autofocus takes to hunt and settle.
BLURscript.js7How far out of focus a slide lands, in pixels on the screen.
DUSTscript.js150The specks of dust floating in the beam.
VIGNETTEscript.js0.55How dark the corners of the projected picture go.
WARMTHscript.js0.3How much the tungsten lamp warms the colours.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • The slide in the projector vanished altogether: moving only the mount down inside its face, with translateY or with overflow hidden on the face, makes Chrome stop painting the face of an object in CSS 3D. The whole slide button moves, and the part that has sunk is cut with a clip-path on the mount.
  • With overflow hidden on the faces of the slides, the others lost the bottom of their mounts too: the date at the bottom did not show. Without overflow the faces show whole.
  • The two faces of a slide lay in the same plane and Chrome showed the wrong one at random (the lab's print, mirrored, over the caption): each sits half a pixel out of the plane, on its own side.
  • The click-stop spring is stiff: integrated with the frame's step (up to 1/30 of a second on the slow machine) the tray swung further and further and never arrived. It is integrated in fixed steps of 1/240 of a second.
  • The ribs between the slots were 20 pixels high and covered the bottom of the slide in the projector, which seemed to float over the slot: now they are 7 high.
  • Painting the six pictures takes more than a second and made the entrance jerky: one picture is painted a frame while the room is still dark, and the lamp only comes on afterwards.
  • The striped tulip field gave a negative index far from the vanishing point and the page stopped; the mesas, coloured through a key colour, left a magenta edge in the antialiasing: they are drawn from their profile worked out for every pixel.
  • A preview click that comes before the lamp is on is put aside and the tour starts as soon as the first slide is in; a key pressed while the tray is turning changes the aim without losing the turn under way.

Libraries

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.

Testing

Tested on 17 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 components