Skip to content
animorio
IT
Back to the library

The contact sheet and the red pencil

Images, TransitionsGSAP, CanvasMediumCompressed weight: Demo 7.6 KBGSAP 28 KBFreeDownload the zip

A photographer’s contact sheet, in the minimal style: twelve black and white frames of the same beach, drawn by the script with film grain. A red grease pencil circles one frame in a single loop, the mark wipes away as the photo lifts off the sheet and grows to the middle with its title underneath, then it goes back into place and the pencil picks another. Clicking picks your own.

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 contact sheet and the red pencil</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/opsz.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>
    <header class="top">
      <span class="top__name">Tidemark</span>
      <span class="top__note">Contact sheet, low tide</span>
    </header>

    <!-- the contact sheet of a made-up photographer: twelve black and white frames of the same shore, drawn by the
         script. A red grease pencil circles one; the frame lifts off the sheet and grows, its title rises under it,
         then it goes back and the pencil chooses another. Clicking a frame chooses it -->
    <section class="sheet" aria-label="Contact sheet" data-sheet>
      <div class="frames" data-frames></div>
      <svg class="pencil" aria-hidden="true" data-pencil><path class="pencil__loop" data-loop d="M0 0"></path></svg>
      <p class="caption" aria-live="polite" data-caption>
        <span class="caption__mask"><span class="caption__title" data-caption-title></span></span>
        <span class="caption__mask"><span class="caption__note" data-caption-note></span></span>
      </p>
    </section>

    <p class="hint" data-hint>Click a frame</p>
  </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.

15 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 contact sheet and the red pencil” from Animorio and fit it into my site

What it does

On warm white paper, at the top the name of a made-up photographer, Tidemark, and on the right Contact sheet, low tide. In the middle the contact sheet: twelve black and white frames in three rows of four, each with a thin black border like the film. They are all photos of the same shore at low tide, drawn by the script: a sun rising over the water, a long pier, two rocks, morning mist, a faraway boat, the setting sun, with film grain and darker corners. When the page opens the prints come up one after the other, pale and washed out, and gain contrast as in a developing tray. Then a red grease pencil circles one frame with a single quick loop, a little crooked, that ends slightly outside where it began. The mark wipes away as the frame lifts off the sheet and flies to the middle growing; the others fade behind it, and under the big photo the title (Sun on the wet sand, for example) and the shot’s settings rise out of a slot. After a moment the photo flies back into place, the sheet lights up again and the pencil picks another; the round goes through three frames and starts again. Clicking or touching a frame picks that one.

How it works

Twelve photos drawn in code

Every frame is a 720 × 480 canvas drawn only once: sky and sea with two linear gradients, the sun with a radial glow clipped at the horizon, the sun’s path made of pale dashes getting wider towards the bottom, the wave ridges, then depending on the scene a pier, the rocks (an uneven shape), a boat, mist, wet sand. Last comes the grain: every pixel gets noise from a seeded generator, so the photo is the same every time, with a touch of warmth and a vignette.

The pencil’s loop

The mark is an SVG path over the whole sheet: an ellipse a little wider than the frame, turned a few degrees, run for 1.14 turns with the radius wobbling slightly and growing towards the end, so the stroke closes further out than where it began, the way a hand does. It draws with stroke-dasharray and stroke-dashoffset going from the whole length (getTotalLength) to zero, and wipes by going negative.

The frame’s flight

The chosen frame moves with GSAP’s transform (x, y, scale with the origin at the top left) from its place to the big rectangle computed by layout(), with the LIFT curve. The black border sits on the child: as the frame grows its thickness drops in proportion, so enlarged it stays a print’s border and not a thick frame. The others drop to 0.14 opacity.

A chain of choices

Every choice is a timeline built on the spot with the measures of that moment: pencil, flight, caption rising out of an overflow hidden window, wait (HOLD), return. At the end it calls the next choice from PICKS. A click stops the chain, puts the sheet back as it was and shows the clicked frame for longer; a resize does the same and starts again with the new measures.

Values to change

NameFileDefaultWhat it changes
PICKSscript.js[4, 9, 1]The frames the pencil picks by itself, in turn (0 is the first, top left).
HOLDscript.js2.4How many seconds a chosen frame stays big.
PENCILscript.js0.85How many seconds the pencil takes to draw its loop.
LIFTscript.js'expo.inOut'The curve of the frame flying to the middle and back.
BIGscript.js0.72How much of the sheet’s width the big frame takes, from 0.5 to 0.95 (all of it on a phone).
GRAINscript.js14How much film grain the photos have, from 0 to 40.

Try them in the Playground

When to use it

For a photographer’s portfolio, the archive of a reportage, a wedding or event gallery, the page of a print lab: wherever there are many photos and you want to show one at a time with a gesture from the work table. The drawn photos can be replaced with real images inside the canvases or with img elements.

On phones

The same, with adapted measures: three columns and four rows, slightly thinner borders and pencil stroke, and the chosen frame grows to the full width. Touching a frame picks it.

With reduced motion

No flights: the pencil’s loop fades in instead of drawing itself, the chosen frame fades out and comes back already big in the middle, the caption fades in, and the return is the same in reverse. When the page opens the sheet fades in.

Pitfalls

  • The frame’s black border grew with the scale: enlarged six times it became an eighteen pixel frame. Its thickness is taken by GSAP to a fraction of the enlargement during the flight and back on the return (also with reduced motion, where the scale jumps in one go).
  • Wiped by taking stroke-dashoffset to the negative length, the loop left a red dot: the round cap at the end of the line stays visible. After the wipe the path goes to opacity 0.
  • On a phone the big frame, at 72% of a narrow sheet, was almost as small as the ones on the sheet: there it takes the whole width.
  • The opening filter (brightness and contrast) does not interpolate to none: the end value must be written in full, brightness(1) contrast(1), with clearProps at the end.

Libraries

GSAP 3.15.0, Newsreader (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 7.6 KB; the libraries: GSAP 28 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