128

35mm Film Strip Gallery

£10 or the whole Vault, £99

A WebGL photo gallery on a strip of 35mm film running between two spinning rolls that fill and empty as you browse: frames sit as negatives and develop into the photograph at the centre, with momentum, motion blur and a lightbox.

advanced
8 more details
webgl-shaderdragmomentumsnapvelocity-reactivelightboxdialog-animationkeyboard-navigation

This demo reads better at your own screen size than in the frame below:

Released

About this effect

Your photographs run across the stage on a strip of near-black 35mm film, with real frame spacing, punched sprocket holes and faint edge numbers. Towards each side the film bends away from the viewer and winds round a roll, the frames foreshortening as they turn. Every frame sits on the strip as a cool, dense negative until it reaches the centre, where it develops into the true colour photograph, the one burst of colour on the page.

Read the full effect overview

Pull the strip and it has weight: it follows your hand, carries on after a throw and settles with a frame centred, while both rolls spin with the film. The rolls track your place in the gallery: the left one fills and the right one empties as you move through, and at either end a short run of black leader shows the strip is finished. A fast pull blurs the film along its travel; at rest it is calm and sharp. Click the developed frame to see it large with its caption.

35mm Film Strip Gallery - GSAP animation effect preview

What's included

9 items
  • Frames develop from negative to full-colour photograph as they reach the centre
  • Film bends away and winds round two matte rolls that spin with it, in real perspective
  • Roll thickness tracks progress: nearly empty against full at the ends, matched in the middle, with a real tapered leader and tail
  • Drag, swipe, wheel and trackpad with momentum and a centred settle; directional motion blur at speed
  • 35mm proportions with punched sprocket holes and faint edge numbers
  • Arrow keys, Home and End on a focusable stage; previous and next buttons
  • Restrained lightbox on a native dialog that grows from the frame, with caption and Escape to close
  • Small API (next, prev, goTo, open, close) and change, open and close events
  • Reduced motion: every frame developed, rolls static, instant steps; no JavaScript or WebGL: a plain photo row

Perfect for

4 use cases
  • Photographer and studio portfolio galleries
  • Selected work rows for creative agencies
  • Brand or campaign image collections with a tactile, analogue feel
  • Exhibition and project archives where each image deserves a moment

How it works

2 sections

The gallery is an ordinary list of images with captions, which stays in the page for screen readers and is the whole gallery when JavaScript or WebGL is unavailable. The script draws the film with three.js: one bent strip whose path runs flat through the middle, bends away from the viewer and wraps round a roll at each side, with the photographs, sprocket holes, edge numbers, negative look and motion blur all drawn by its shader.

GSAP moves the strip (the throw and settle after a drag or wheel gesture, and frame-by-frame steps), animates the lightbox opening from the frame, provides the frame clock that reads the strip's speed for the blur and the roll spin, and rebuilds the gallery live when the visitor's reduced-motion preference changes. Everything it adds is removed on revert().

Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying 35mm Film Strip Gallery opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

1. Add to your HTML <head> (the probe is required: it hides the fallback photo row before first paint on browsers that are about to get the canvas):

Code snippet omitted: it ships with the download.

2. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

If your page already loads three.js as a module, set window.THREE from it and load script.js after that instead.

3. Add the gallery markup anywhere in your <body>:

Code snippet omitted: it ships with the download.

Using It With Your Own Design

What the effect needs from your markup: a .film-strip root with a .film-strip__stage containing the .film-strip__track list, one .film-frame per photograph. Each frame holds an <img> and, optionally, a .film-frame__caption, which feeds the caption bar and the lightbox (the image's alt is the fallback). data-full on an image is the larger file the lightbox swaps in once it has loaded. The caption bar is optional: the script fills [data-film-index] and [data-film-title] and wires [data-film-prev] / [data-film-next] when they exist. Several galleries on one page each work independently.

What is only the demo's styling: the dark ground, Mona Sans, the red accent on the frame number and focus rule, the round buttons, the cue line and the light paper lightbox. Recolour the film with two custom properties on .film-strip, each a hex colour: --film-base (the film itself), --film-mark (edge numbers). Set the frame size with --pitch, the width of one frame; everything else on the strip, including the rolls, scales from it. The canvas, rolls, sprocket holes, edge numbers and lightbox are generated; you do not author them.

CSS the effect depends on:

  • html.gl (from the head probe) gives the stage its height and clips the photo list to a screen-reader-only box, so the fallback row never flashes before the canvas.
  • .film-strip.is-live fades the canvas in and reveals the caption bar once the first frame has drawn.
  • .film-strip__gauge is an invisible ruler var(--pitch) wide; the script reads the frame width from it.
  • The stage uses touch-action: pan-y, so a vertical swipe still scrolls a phone page.
  • Without html.gl (no JavaScript, no WebGL, or three.js failed to load) the same markup is a plain, scroll-snapping row of photographs with visible captions.

Options

Set these on the .film-strip element.

Attribute Values Default Description
data-start Frame index from 0 0 Frame centred on load
data-develop Frames, 0.2 and up 0.75 How far from the centre a frame is still partly developed; larger values develop neighbours sooner
data-momentum 0 and up 1 How far a throw carries the strip; 0 settles on the nearest frame
data-blur Pixels, 0 and up 8 Maximum motion blur along the film on a fast pull; 0 turns it off
data-flicker 0 to 1 0.5 How much the developed print's brightness wavers at speed; 0 keeps it steady
data-lightbox true, false true Whether clicking the developed frame (or Enter) opens the lightbox

Accessibility

  • Ends: at the first and last frame a short run of black leader leads onto a nearly empty roll; dragging past either end resists and springs back.
  • Keyboard: the stage is focusable and shows an accent rule under the centred frame when focused. Left and Right step frame by frame, Home and End go to the first and last photograph, Enter or Space opens the centred frame.
  • Screen readers: the photographs stay in the document with their real alt text and captions while the canvas draws them; the canvas is hidden from assistive technology. The caption bar is a polite live region that announces the frame now at the centre. Edge numbers are drawn into the canvas, so they are neither read nor contrast-checked.
  • Lightbox: a native <dialog>, so focus moves into it and returns to the strip, Escape closes it, and so do the Close button and a click on the backdrop.
  • Reduced motion: every frame shows developed, there is no momentum or blur, the rolls stay still, and arrow keys, buttons and wheel step instantly. The lightbox opens without animation. Changing the preference while the page is open rebuilds the gallery on the same frame.
  • No JavaScript or no WebGL: the markup is an ordinary scrollable row of photographs with captions.
  • Touch: horizontal swipes drive the strip; vertical swipes still scroll the page. The wheel and trackpad move the strip whenever the pointer is over it.

Dependencies

Required:

  • GSAP 3.12+ (core only; no plugins). GSAP moves the strip (throw, settle, steps and wheel glide), animates the lightbox, supplies the frame clock and handles the reduced-motion rebuild and teardown. The demo is pinned to GSAP 3.15.0.
  • three.js (demo pinned to 0.180.0), loaded as an ES module through the import map above. The shaders draw the film, its bend round the rolls, the develop and the blur.

Browser Support and Performance

Any browser with WebGL 2 draws the strip; anything else gets the photo row. The whole strip is one draw call over one texture of all the photographs, plus two small rolls, and it only renders while the strip moves or resizes. If you need to lighten it, lower data-blur first (each blurred pixel samples the film seven times), then use smaller source images.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.