Skip to content
animorio
IT
Back to the library

Photos stretched by the scroll

Images, ScrollGSAP, CanvasMediumCompressed weight: Demo 7.3 KBGSAP 28 KBLenis 5.3 KBFreeDownload the zip

Scroll inside the preview

In the minimal style, a photo essay on paper: five photos in a column that, as you scroll, leave a trail like a slit-scan camera. Their last row of pixels stretches into streaks of their own colours, longer the faster you go, and when you stop it draws back in on a spring; the titles widen with the speed and in the corner the shutter speed slows down.

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>Photos stretched by the scroll</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/anybody@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- the photos are drawn on this canvas, under the words: the page only keeps their places -->
  <canvas class="film" data-film aria-hidden="true"></canvas>

  <header class="top">
    <p class="mark">Halden</p>
    <p class="shutter" aria-hidden="true"><span class="shutter__k">Shutter</span> <span class="shutter__v" data-shutter>1/500 s</span></p>
  </header>

  <main class="page">
    <section class="intro">
      <h1 class="title" data-title>Long light</h1>
      <p class="dek">Five photographs from a slow summer. Scroll fast and they leave a trail.</p>
    </section>

    <section class="roll" data-roll>
      <figure class="shot shot--a" data-shot data-scene="dune" data-ratio="0.8">
        <div class="shot__slot" data-slot role="img" aria-label="Dune at five"></div>
        <figcaption class="shot__cap"><span class="shot__t" data-cap>Dune at five</span><span class="shot__m">Erg Chebbi, June</span></figcaption>
      </figure>
      <figure class="shot shot--b" data-shot data-scene="lanes" data-ratio="1.5">
        <div class="shot__slot" data-slot role="img" aria-label="Lane four"></div>
        <figcaption class="shot__cap"><span class="shot__t" data-cap>Lane four</span><span class="shot__m">Lido pool, July</span></figcaption>
      </figure>
      <figure class="shot shot--c" data-shot data-scene="tulips" data-ratio="0.8">
        <div class="shot__slot" data-slot role="img" aria-label="Rows of tulips"></div>
        <figcaption class="shot__cap"><span class="shot__t" data-cap>Rows of tulips</span><span class="shot__m">Lisse, April</span></figcaption>
      </figure>
      <figure class="shot shot--b" data-shot data-scene="umbrellas" data-ratio="1.5">
        <div class="shot__slot" data-slot role="img" aria-label="Beach from above"></div>
        <figcaption class="shot__cap"><span class="shot__t" data-cap>Beach from above</span><span class="shot__m">Rimini, August</span></figcaption>
      </figure>
      <figure class="shot shot--a" data-shot data-scene="neon" data-ratio="0.8">
        <div class="shot__slot" data-slot role="img" aria-label="Late bar"></div>
        <figcaption class="shot__cap"><span class="shot__t" data-cap>Late bar</span><span class="shot__m">Porto, September</span></figcaption>
      </figure>
    </section>

    <p class="end">End of the roll</p>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.

27 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 “Photos stretched by the scroll” from Animorio and fit it into my site

What it does

A long paper-coloured page, the photo essay of a made-up travel journal, Halden. At the top the huge title, Long light, which comes in stretching from narrow to wide, and a line saying: five photographs from a slow summer, scroll fast and they leave a trail. Then five photos drawn by the script, in a loose column, some on the left and some on the right, each with its title and place beside it: dunes at sunset, the lanes of a pool with red and white ropes, rows of coloured tulips running to the horizon, a beach seen from above with segmented umbrellas, a bar at night with neon signs. Still, they are just photos. As you scroll, every photo leaves a trail like a slit-scan camera: the last row of pixels on the side it is moving away from stretches into streaks of its own colours (the lanes become a red and blue barcode, the tulips solid bands, the neon pink and orange drips), longer the faster you go, fading into the paper towards the end. When you stop the trail draws back in on a spring, goes one pixel past the edge and comes back. The titles of the photos widen with the speed too, and in the bottom right corner a shutter speed in red slows down from 1/500 s to 1/4 s as the trail grows, and gets fast again when you stop.

How it works

The photos on a fixed canvas

The photos are not images on the page: the page only keeps their places (empty divs with aspect-ratio) and the captions. A fixed canvas as big as the window sits under the text, and on every frame the script reads with getBoundingClientRect where each place is and draws the photo there. So the trail can leave the frame without being cut.

The speed

On every frame GSAP's ticker measures how much scrollY has changed (Lenis makes the scrolling smooth) and divides it by deltaRatio, so the speed is the same at 60 and 120 Hz. The sign says which way the trail goes: scrolling down the photos rise and the trail comes out of the bottom, scrolling up out of the top.

The trail on a spring

The length of the trail does not follow the speed at once: it is a spring (SPRING) chasing speed × STRETCH, up to MAX_TRAIL of the photo's height. When you stop the spring goes back to zero passing a little beyond: the trail draws back in with a settle instead of vanishing.

The slit

The trail is a drawImage of just two rows of the photo's pixels, taken just inside the edge (where the vignette has not darkened them yet), stretched to the length of the trail: every column of the photo becomes a streak of its own colour. Over it, a gradient from transparent to the paper's colour makes it vanish towards the end (FADE).

The words that run

The photo titles are in Anybody, a variable typeface with a wdth axis from 50 to 150: the same spring as the trail takes the width from 100 to 150. The shutter speed in the corner picks one of eight values, from 1/500 s to 1/4 s, depending on how long the trail is.

Values to change

NameFileDefaultWhat it changes
STRETCHscript.js16How much the trails stretch for the same speed.
MAX_TRAILscript.js0.6The longest a trail can be, as a part of its photo's height.
SPRINGscript.js[0.16, 0.7]The trail's spring: how fast it follows the speed and how much of its swing it keeps (more wobbles more).
FADEscript.js0.7Where along the trail it starts to fade into the paper (0 = at once, 1 = only at the end).
STRETCH_TYPEscript.jstrueThe photo titles widen with the speed.
SMOOTHscript.jstrueSmooth scrolling with Lenis.

Try them in the Playground

When to use it

For a long photo story: a travel magazine's reportage, a photographer's page with a series, the diary of a summer on a blog, the gallery of a hotel or a beach club. Whoever scrolls slowly sees clean photos; whoever rushes sees them leave a trail.

On phones

The same animation with the sizes adapted: the photos sit in a column almost as wide as the screen, the titles under the photos, and the trail follows the finger's scrolling.

With reduced motion

No trails and no widening words: the scrolling is the browser's normal one and the photos stay clean; the title fades in.

Pitfalls

  • A trail taken from the very last row of pixels came out grey and dirty: the photo's vignette darkens exactly the edge. Now the slit sits at 97.5% of the height (at 2.5% scrolling up).
  • With the masthead fixed at the top, the photos passed under it and the journal's name ended up over the black of the bar: the masthead scrolls with the page and only the shutter speed stays fixed, on a scrap of paper in the corner.
  • One img element per photo with a CSS trail would be cut by its own box: that is why everything goes through a single canvas, under the text (the captions stay readable even over the trail).

Libraries

GSAP 3.15.0, Lenis 1.3.26, Anybody (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 7.3 KB; the libraries: GSAP 28 KB, Lenis 5.3 KB.

Testing

Tested on 19 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 animations