104

Photo Reel Preloader

£10 or the whole Vault, £99

A preloader made from the page's own photographs: a small frame flicks through a reel as the images load, a counter climbs to 100, then the last photograph grows out of the frame and becomes the full-bleed hero.

advanced
5 more details
load-sequenceshared-elementcount-upclip-pathstagger

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

Released

About this effect

Most preloaders are a number on a flat overlay followed by a curtain. This one is made from the photographs your page is already loading. A small portrait frame sits on a dark ground and flicks through a reel of images, each one arriving as it actually finishes downloading, while a tabular-figure counter climbs beside it.

Read the full effect overview

At 100 the frame holds on the final photograph for a beat, then that photograph grows out of the frame and lands exactly on your hero image, crop for crop. There is no wipe and no swap: the loader becomes the page. The counter and caption lift away as it grows, and the nav and headline stagger in over the landed image.

It runs on GSAP core alone, is configured with a few data attributes, and leaves a plain, readable hero when JavaScript is off, when GSAP fails to load, or when the visitor prefers reduced motion.

Photo Reel Preloader - GSAP animation effect preview

What's included

8 items
  • Loader built from your own photographs, so the wait previews the work instead of hiding it
  • The final photograph grows from the small frame into the full-bleed hero and lands on its exact crop
  • Honest progress: the counter follows images that have actually loaded and holds short of 100 until the page has
  • Hard time cap so a slow or missing image never traps the visitor, and a minimum duration so a cached load still reads
  • Reel skips images that fail to load and falls back to a clean fade if the hero image is unavailable
  • Once-per-session mode for repeat visitors, a replay() call, and a photoreel:complete event for chaining
  • Scroll locked only while the loader is up; revert() removes every listener, style and generated node
  • Works with any hero size: it lands on the hero image's real box, full-bleed or not

Perfect for

5 use cases
  • Photographer and studio portfolios where the first thing a visitor sees should be the work
  • Architecture, interiors and travel sites that open on a single strong hero photograph
  • Agency case-study pages that want a branded load moment without a curtain
  • Image-heavy landing pages where the wait for photographs is real and worth covering
  • Product launches that end the load on the hero shot and chain their own animation off the complete event

How it works

3 sections

The loader is an ordinary element on your page: a frame holding a handful of reel images, a counter and a caption. The page's hero image stays where it is in your markup and is the photograph the reel always ends on, so there is one source of truth for what the visitor lands on.

Progress is tied to real loading rather than a timer. The count rises with the images that have settled, stops short of the finish until the browser reports the page loaded, and has both a minimum and a maximum duration so it never flashes past or hangs. The reel only ever shows an image that has arrived.

The handoff is the signature. The last frame and the hero are the same photograph at two sizes, and the growth between them keeps the crop continuous, so the visitor sees one image open up rather than a transition between two. Everything the sequence hides is gated behind a has-js class set in the head, so no JavaScript means no loader and a normal hero.

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 Photo Reel Preloader 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>, before the stylesheet:

Code snippet omitted: it ships with the download.

2. Add the loader as the first thing inside <body>:

Code snippet omitted: it ships with the download.

3. Mark your hero image, and the elements that should stagger in once it lands:

Code snippet omitted: it ships with the download.

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

Code snippet omitted: it ships with the download.

Using It With Your Own Design

What the effect needs from your markup: one loader element carrying data-photo-reel, with a data-photo-reel-frame inside it holding your reel images (data-photo-reel-image). The reel always ends on the image marked data-photo-reel-hero, which lives in your page as a normal hero image; you do not repeat it in the reel. The counter (data-photo-reel-count), the caption (data-photo-reel-caption) and anything marked data-photo-reel-chrome are optional. The script never reads a class name, so every reel-loader__* and folio-* class is only there to hang styles on.

What is only the demo's styling: the photographer's landing page (nav, headline, scrim, Replay button), the fonts, the caption layout and the counter's size and position. Delete or restyle any of it. The frame can be any size or shape; the script measures it when the photograph starts to grow.

CSS the effect depends on (each rule is marked CONTRACT in style.css):

  • .reel-loader { display: none } with .has-js .reel-loader { position: fixed; inset: 0; ... }: the loader exists only when the script runs, and .has-js is removed again if GSAP fails to load.
  • .has-js .reel-loader[data-state="done"] { display: none }: how the script retires the loader once the hero has landed.
  • Reel images are absolutely positioned inside the frame with object-fit: cover and start visibility: hidden.
  • The hero image must cover its box with object-fit: cover and a centred object-position. That centred crop is what the growing photograph lands on, so a different object-position would jump at the end.
  • Stacking inside the loader: frame at z-index: 1, the script's growing photograph at 2, the caption and counter at 3. Do not give the wrapper around them a z-index, or the photograph grows behind the frame.
  • html.photo-reel-lock { overflow: hidden }: the scroll lock while the loader is up.
  • .has-js [data-photo-reel-reveal] { opacity: 0; visibility: hidden; transform: translateY(22px) }: the reveal start state, restored under prefers-reduced-motion.
  • .has-js [data-photo-reel-fade] { opacity: 0 }: the fade-only start state for overlays, restored under prefers-reduced-motion.

Use the same photograph file for the hero that the page would show anyway. The loader copies the hero image's currentSrc, so a srcset works and the file is only downloaded once.

Options

All options go on the data-photo-reel element.

Attribute Values Default Description
data-min-duration Seconds 1 Shortest the reel may take, even when every image is cached. Keeps a fast load readable
data-max-duration Seconds 6 Hard cap. The counter completes by this time whatever is still loading; images that never arrived are skipped
data-replay-duration Seconds 1 Reel length when replay() runs, when everything is already cached
data-hold Seconds 0.15 Pause on the final photograph at 100 before it grows
data-expand-duration Seconds 0.9 Length of the frame-to-hero growth
data-once true, false false When true, the sequence runs once per browser session; repeat visits go straight to the settled hero
data-once-key Any string Page path The sessionStorage key suffix for data-once. Share one key across pages to show the loader once per site

Element Hooks

Attribute Where Description
data-photo-reel The loader Root of the sequence; options live here. One per page
data-photo-reel-frame Inside the loader The small frame the reel plays in and the photograph grows from
data-photo-reel-image Inside the frame Reel images, shown in DOM order. data-caption sets the caption text while it shows
data-photo-reel-count Inside the loader Receives the padded counter text, 000 to 100
data-photo-reel-caption Inside the loader Receives each photograph's data-caption (the hero falls back to its alt)
data-photo-reel-chrome Inside the loader Lifts away as the photograph starts to grow
data-photo-reel-hero Your hero <img> The photograph the reel lands on and grows into
data-photo-reel-fade Anywhere in the page Fades in, without moving, as soon as the photograph has landed. For a scrim or tint behind your hero text, so it does not snap in with the page
data-photo-reel-reveal Anywhere in the page Staggers in (rise and fade) just after the fades begin
data-photo-reel-replay Any <button> Optional: re-runs the sequence without reloading. Disabled while it runs and under reduced motion

Accessibility

  • Reduced motion: under prefers-reduced-motion: reduce there is no reel and no growth. The loader never shows, the hero and headline are visible immediately, and the CSS restores the reveal targets even before the script runs. If the preference changes while the page is open, the page settles to the finished hero.
  • No JavaScript: the loader is hidden unless the has-js class is present, so without JavaScript, or when GSAP fails to load, visitors get a plain page with the hero visible.
  • Screen readers: the reel, counter and caption are aria-hidden; a role="status" line announces that photographs are loading. The page content is in the document the whole time, so nothing is withheld from assistive technology while the loader plays.
  • Never trapped: the hard time cap completes the sequence however slow the network is, and scroll is only locked while the loader is on screen.
  • Replay button: a native <button>, disabled while the sequence runs.

Photography

The demo's photographs are art-directed derivatives of Pexels photographs, used under the Pexels licence: heath track in fog (pexels.com/photo/36344918), aerial surf (5851472), ridge over cloud (26953466), pines in fog (15222306), sea cliffs (7112541) and the misty conifer valley hero (10762369). Replace them with your own work: swap the files in assets/img/, or edit assets/img-manifest.json and rebuild the derivatives.

Dependencies

Required:

  • GSAP 3.12+ (core only). The demo is built against GSAP 3.15.0.

No plugins, no build step.

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