
Projects in a row with a ring
Scroll, Images, Text
NewFree
Scroll inside the preview
The opening of a long feature: on light paper a huge serif headline, and the photo shows only inside its letters. As you scroll the headline grows into one letter until the photo comes out and fills the screen, then it pulls into a frame with its caption and the story begins below, with a drop cap.
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 headline that opens the photo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bodoni-moda@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/opsz.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/opsz-italic.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/schibsted-grotesk@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the opening of the feature: a tall section with a pinned screen inside; scrolling through it opens the photo -->
<section class="opener" data-opener>
<div class="stage" data-stage>
<header class="mast" data-fade="up">
<div class="mast__row in">
<a class="mast__name" href="#">Halyard</a>
<span class="mast__tag">A quarterly of the sea</span>
<span class="mast__date">Autumn 2026</span>
</div>
<span class="mast__rule" data-rule></span>
</header>
<div class="cover">
<p class="kicker" data-fade="up">
<span class="in"><span class="kicker__section">The Long Read</span><span class="kicker__sep" aria-hidden="true"></span><span class="kicker__place">North Atlantic</span></span>
</p>
<h1 class="cover__title" data-headline>Salt</h1>
<!-- an empty box as tall as the headline: the script draws the letters over it -->
<div class="cover__slot" data-slot aria-hidden="true"></div>
<p class="standfirst" data-fade="down">
<span class="in">On the last island where the weather still decides when the boats go out, a village of forty-one people has learned the slow art of waiting.</span>
</p>
<p class="byline" data-fade="down">
<span class="in"><span>Words by <b>Ines Harlow</b></span><span>Photographs by <b>Tomas Vey</b></span><span class="byline__time">18 min read</span></span>
</p>
</div>
<p class="hint" data-hint>
<span class="in">Scroll to read
<svg class="hint__arrow" data-arrow viewBox="0 0 12 18" aria-hidden="true"><path d="M6 1v15M1 11l5 5 5-5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
</p>
<!-- the photo: at first it shows only through the letters of the headline, at the end it is the whole frame -->
<svg class="art" data-art aria-hidden="true">
<defs>
<clipPath id="headline-clip" clipPathUnits="userSpaceOnUse">
<text class="art__word" data-word></text>
</clipPath>
</defs>
<g data-art-in>
<!-- the clip sits on a group: on the image itself it would grow with the image -->
<g clip-path="url(#headline-clip)"><image data-photo-in preserveAspectRatio="xMidYMid slice"/></g>
<image data-photo-full preserveAspectRatio="xMidYMid slice" opacity="0"/>
</g>
</svg>
<p class="caption" data-caption>
<span>The harbour at Skarvik at ten to six, before the first boat leaves.</span>
<span class="caption__credit">Tomas Vey for Halyard</span>
</p>
</div>
</section>
<article class="story">
<p class="story__lead" data-reveal><span class="drop" aria-hidden="true"><span data-drop>E</span></span><span class="sr">E</span>very morning at a quarter past five, Aksel Dam walks to the end of the stone pier, takes off one glove and holds his hand up to the wind. He does not check his phone. The forecast, he says, is written by people who have never had to row home. If the air smells of iron, the boats stay in. If it smells of kelp and nothing else, the boats go out. On the morning I met him it smelled, he told me, of both, so we stood there for a long time and said nothing.</p>
<p data-reveal>Skarvik has forty-one residents, one shop that opens when its owner remembers, and a harbour older than the church. For three centuries the village lived on cod, salted and dried on the flat rocks behind the houses until the whole bay smelled like a kitchen. The racks are still there, grey and silver, though nobody has hung a fish on them since the plant across the sound closed in 1998.</p>
<p data-reveal>What the village kept is the salt. In a shed with a turf roof, Marit Joensen boils seawater in three iron pans over a driftwood fire, sixteen hours at a time, until what is left at the bottom looks like snow that has been told a secret. She sells it in paper bags to restaurants in cities she has never visited. The waiting list is two years long. She finds this funny.</p>
<blockquote class="quote" data-quote>
<span class="quote__rule" data-quote-rule></span>
<p data-quote-text>The sea gives you the salt. It just wants you to stay up all night for it.</p>
<span class="quote__rule" data-quote-rule></span>
</blockquote>
<p data-reveal>It would be easy to call Skarvik a place that time forgot. The people who live here would disagree. Time, they will tell you, is the one thing they have paid close attention to: the hour of the tide, the week the eider ducks come back, the single afternoon in March when the light returns over the ridge and every kitchen door in the village is opened at once.</p>
</article>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
<script src="script.js"></script>
</body>
</html>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.
33 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The headline that opens the photo” from Animorio and fit it into my siteA section (1 + LENGTH) screens tall holding a sticky stage 100lvh high. A single ScrollTrigger with scrub carries a timeline from 0 to 1, and the timeline moves four numbers: how much the headline has grown, how big the photo is, how much of the paper is covered and how closed the frame is. One function draws them.
Over the page there is an SVG as big as the screen, with the photo in an <image> and a clipPath made of text: the word WORD in Bodoni Moda 900. The clipPath sits on a group around the image, not on the image: there it would grow with the photo. The script measures the letters with measureText and picks the size that fills the width; in the page an empty box as tall as the headline keeps its place.
The letter LETTER is drawn alone in a canvas and a two-pass distance map finds its thickest point, the one farthest from the edge. The headline grows around that point with a scale of GROW to the power of the progress, so it always comes closer at the same pace, while the point slides to the middle of the screen: the letter covers everything as soon as possible.
Near the end of the growth a second copy of the photo, without the clip, goes from transparent to full: if the letter were not enough, the paper still disappears. Then clip-path: inset() pulls the photo in by FRAME from the sides and from top and bottom, while the photo goes from 1.06 to 1 and settles. The caption rises under the frame.
The photo is a layered SVG drawing (sky, sun, haze, hills, the sea with its glints, a rocky headland, lighthouse, boat, grain and vignette) baked once into a JPEG on a canvas: moving a picture costs nothing. Below, each paragraph rises with its own ScrollTrigger, the drop cap comes out of a box that cuts it, and the quote’s rules draw from the middle.
| Name | File | Default | What it changes |
|---|---|---|---|
WORD | script.js | 'Salt' | The headline, one or two words: the photo shows only inside its letters. On a phone two words go on two lines. |
GROW | script.js | 40 | How many times the headline grows by the end of the zoom. |
LETTER | script.js | 2 | The letter the zoom goes into, counting from 0 and without spaces: with “Salt”, 2 is the “l”. |
LENGTH | script.js | 2.6 | How long the opening lasts, in window heights of scrolling. |
SCRUB | script.js | 0.9 | The seconds the opening takes to catch up with the scroll: higher is softer. |
FRAME | script.js | 0.07 | How far the photo pulls in from the sides at the end, as a share of the width. |
To open a long article, a feature, a case study or a project page: a short, strong headline and a photo worth the whole screen. It works best with short words and thick letters, in a black serif or a heavy sans.
The headline fills the width of the narrow screen (two words go on two lines) and the zoom goes into the same letter until it covers everything. The final frame is upright, with more room below for the two-line caption.
Nothing grows and nothing moves: as you scroll the words fade, the letters with the photo dissolve and the photo already in its frame fades in their place, with the caption. Paragraphs and quote appear with a fade only.
GSAP 3.15.0, Bodoni Moda (font variabile) 5.3.0, Newsreader (font variabile) 5.3.0, Schibsted Grotesk (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP + ScrollTrigger 45 KB.
Tested on 12 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.