155

Nested Scroll Zoom

£7
or the whole Vault, £99

Pinned scroll section that flies the camera through a window in one picture into the next, and on through every opening in turn, until the last picture lands full bleed under a closing message.

ScrollTrigger advanced
5 more details
pinningscrubzoomparallaxscroll-storytelling

Released

About this effect

A Droste zoom you drive with the scrollbar. Pictures sit inside one another, each visible through a window in the one before, and a single long scroll carries the visitor through every opening in turn: through the wall into the room, through the room into the courtyard, through the courtyard to the desert. The first frame already shows the whole journey, nested and tiny, which is what makes people start scrolling.

Read the full effect overview

The zoom keeps an even pace however deep it goes, and slows at each picture so that the eye can read it before passing through. A line of type belongs to each picture and crosses over as it takes the frame. The last picture brakes into its landing as the final frame flies past it, finishes pixel-sharp and exactly full bleed at any screen shape, and the closing text arrives on top of it.

Nested Scroll Zoom - GSAP animation effect preview

What's included

9 items
  • Fly through any number of pictures on one pinned scroll, driven entirely by the scrollbar and fully reversible
  • Even zoom speed from the first opening to the last, with no lurching as the camera goes deeper
  • A slow-down at every picture, tunable with one attribute, that eases rather than stops
  • A slight depth shift inside each opening, so the view beyond behaves like something further away
  • The last picture lands at 1:1 with no transform left on it, exactly full bleed at any viewport shape
  • A headline per picture and a closing heading and paragraph, cross-fading on their own timing
  • On phones the opening stays a window: the outer picture fits to the opening and sits on flat ground in its own colours
  • The last layer takes an image or a muted looping video, and two copies on a page run independently
  • Reduced-motion, no-JS and blocked-CDN visitors get the last picture full bleed with all of the text on it

Perfect for

5 use cases
  • Hotels, retreats and destinations that want the arrival to feel like walking there
  • Architecture and interior studios moving from street to room to view
  • Property and venue pages that lead a visitor through a space in one gesture
  • Food, drink and travel brands telling a from-here-to-there story
  • Campaign pages built around one memorable scroll set piece

How it works

2 sections

Each picture has a transparent opening, and you tell the effect where it is. From those rectangles the effect works out one camera path through the whole stack, so the openings stay perfectly registered at every scroll position with no gaps. Pictures the camera has passed are switched off to keep the page light, and the last one is laid out at its final size so that it finishes without being scaled at all.

A pinned ScrollTrigger scrubs the journey and then releases into normal scrolling. Attributes tune the pin distance, the scrub smoothing and how long the camera lingers at each picture. Touch screens get a shorter pin and their own framing, and reduced motion gets a static full-bleed layout with no pin.

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

The Vault opens this source and the whole library.

Each product gives you the HTML, CSS and JavaScript source, the full documentation and an AI setup prompt, licensed for 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.

One payment, over 300 products, and future additions to the library.

Documentation

Quick Start

1. Add to your HTML <head>:

In your downloadThe working code for this step, in index.html.

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

In your downloadThe working code for this step, in index.html.

3. Add the section anywhere in your <body>. Outermost picture first:

In your downloadThe working code for this step, in index.html.

Using It With Your Own Design

What the effect needs from your markup: .nsz > .nsz-stage > .nsz-layers > .nsz-layer, outermost picture first. Each layer holds one <img> or <video> with width and height attributes, because the effect works from those numbers rather than waiting for the file. Every layer except the last needs data-hole="x y w h": its opening in that file's own pixels. Use as many layers as you like, from two upwards.

.nsz-line elements pair up with layers in order, so the first line belongs to the outer picture. There is one line per layer except the last, which gets .nsz-payoff instead. The first line is visible on load.

Any image or video can go in the last layer. Swap the <img> for a muted looping video with its real dimensions:

In your downloadThe working code for this step, in index.html.

The layers with openings need transparency, so they stay images. The last layer lands with object-fit: cover and honours a percentage object-position that you set on the media.

Preparing a set of pictures:

  1. Make each outer picture with its opening filled in one flat colour that appears nowhere else, such as pure green. A rectangle wider than the next picture's own shape works best, because the picture beyond will overscan it.
  2. Key that colour out so that the opening is truly transparent, and export as WebP or PNG with alpha. Check the rim for any leftover colour.
  3. Measure the opening's bounding box in source pixels and put it on data-hole. Any image editor's selection readout will do, or run this in Node with sharp:

In your downloadThe working code for this step, in script.js.

Compose every inner picture knowing that a little of its edges will be cropped by the opening in front of it, and keep its own opening clear of the crop. Pictures do not need to be the same size or shape.

On a phone: a cover-fitted picture would push its opening wider than the screen, which loses the window. So on tall screens the effect limits every opening to about 70 percent of the width. The outer picture then stops short of the screen top and bottom and sits on flat ground. Set those two colours to match the picture's top and bottom edges:

In your downloadThe working code for this step, in style.css.

Each line positions itself under its picture's opening, or in the band below it on a phone.

What is only the demo's styling: the font, type sizes, line colours and the soft shade behind some lines (--nsz-line-colour and --nsz-line-shade, set per line in style.css), the closing scrim's colours. Pick line colours per picture and check them against what sits behind them.

CSS the effect depends on: .nsz-stage keeps overflow: hidden and a full-viewport height under .has-js. The layers must stay absolutely positioned with transform-origin: 0 0. The static rules at the top of style.css are the fallback, so keep the flight's rules inside the .has-js motion block.

Options

Set these on .nsz.

Attribute Values Default Description
data-nsz-distance Percent of the viewport height 160 per step between pictures Pin distance for the whole journey, so five pictures default to 640. Touch screens use 70 percent of it.
data-nsz-scrub Seconds, or true 0.9 Scrub smoothing. true follows the scrollbar exactly.
data-nsz-hold 0 to 1 0.72 How much the camera slows at each picture. 0 is a constant zoom.

Set this on .nsz-layer.

Attribute Values Description
data-hole x y w h in source pixels The picture's opening. Required on every layer except the last.

Accessibility

  • Reduced motion: no pin and no zoom. The last picture sits full bleed with every line and the closing text on it.
  • No JavaScript or a blocked CDN: the same static layout.
  • Screen readers: the lines and closing text are ordinary headings and paragraphs in reading order. Give each image a useful alt, or an empty one if the text already says it.
  • Contrast: the demo's type clears 3:1 for large text on its pictures, and the closing paragraph clears 4.5:1. Re-check when you change pictures, and adjust .nsz-scrim, --nsz-line-colour and --nsz-line-shade.
  • Touch: a shorter pin, and the framing described above.

To tear the effect down, for example when leaving a route in a single-page app:

In your downloadThe working code for this step, in script.js.

This removes the pin, triggers and inline styles, and restores the static layout.

Dependencies

  • GSAP 3.12+ (the demo uses 3.15.0)
  • ScrollTrigger

Fonts are demo styling: the demo uses Instrument Serif from Google Fonts.

Also in your download: the full source, worked examples, the events and programmatic API, and the class reference.

£7