
Words that melt into each other
Text, Scroll, SVG
NewFree
Scroll inside the preview
As you scroll, a little steam train rides through a layered landscape: countryside, a bridge over a river, a dark tunnel where the windows light up, the sea at dusk. At every station a ticket card comes in with its text and the name swings on a hanging sign.
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 train across the page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<!-- the ride: taller than the screen; the stage sticks while the landscape rolls past the train -->
<section class="ride" data-ride>
<div class="stage" data-stage>
<svg class="world" data-world aria-hidden="true"></svg>
<header class="bar">
<a class="brand" href="#">Larkspur <span>Rail Journeys</span></a>
<a class="bar__link" href="#">Timetable</a>
</header>
<!-- one card per stop; data-sign is the name painted on the swinging station sign -->
<div class="cards">
<article class="card card--intro" data-card="intro">
<p class="card__kicker">Slow trains, long windows</p>
<h1 class="card__title" data-split>Four stops and one long window seat.</h1>
<p class="card__hint">Scroll to ride
<svg class="card__arrow" viewBox="0 0 16 22" aria-hidden="true"><path d="M8 2v17M2 13l6 6 6-6"/></svg>
</p>
</article>
<article class="card" data-card="stop" data-sign="Meadowmere">
<p class="card__kicker">The countryside</p>
<h2 class="card__title" data-split>Bread at six, cows at seven.</h2>
<p class="card__text">A halt so small the train stops on request. Hay bales, a bakery by the level crossing and a guard who waves at every field.</p>
</article>
<article class="card" data-card="stop" data-sign="Rivercross">
<p class="card__kicker">The river</p>
<h2 class="card__title" data-split>Slow down, the herons are watching.</h2>
<p class="card__text">The line crosses the Wend on an iron bridge. Pick the left side of the carriage: the water is wide here and the light is soft all morning.</p>
</article>
<article class="card" data-card="stop" data-sign="Underhill">
<p class="card__kicker">The tunnel</p>
<h2 class="card__title" data-split>Two minutes of dark.</h2>
<p class="card__text">The carriage lamps come on, somebody always laughs, the tea goes cold. Underhill is the only station on the line built inside a hill.</p>
</article>
<article class="card" data-card="stop" data-sign="Saltwick Bay">
<p class="card__kicker">The coast</p>
<h2 class="card__title" data-split>Salt on the windows.</h2>
<p class="card__text">Last stop. Chips on the harbour wall, a lighthouse that winks at the evening train and a sea that is never the same colour twice.</p>
</article>
<article class="card card--outro" data-card="outro">
<p class="card__kicker">Larkspur Rail Journeys</p>
<h2 class="card__title" data-split>The next train leaves at dawn.</h2>
<a class="ticket" href="#">
<span class="ticket__fill" aria-hidden="true"></span>
<span class="ticket__label">Book a window seat</span>
<svg class="ticket__arrow" viewBox="0 0 22 14" aria-hidden="true"><path d="M1 7h18M13 1l6 6-6 6"/></svg>
</a>
</article>
</div>
</div>
</section>
</main>
<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="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.
52 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 train across the page” from Animorio and fit it into my siteThe section is many screens tall and its first child is a sticky stage, 100lvh tall with overflow: hidden. A paused GSAP timeline carries two numbers: d, the distance travelled (in screen widths), and tx, the train coming in from the left (-1), standing in its place (0) and leaving on the right (1). Between stations d moves on with power2.inOut, then holds for a while: that is the stop. A ScrollTrigger with scrub ties the timeline to the scroll, and Lenis makes it smooth.
Everything is one SVG as big as the stage, redrawn by JavaScript when its size changes. Each layer is a group moved by -d × its speed (clouds 0.04, far hills 0.14, nearer ones 0.38 and 0.68, the track 1, the poles 1.32, the grass 1.75); SPEED widens or narrows these differences. The things on the track sit where the line meets them: bridge, tunnel, sea and stations are at a fixed distance. The far layers have two versions, countryside and coast, which cross-fade while the train is in the tunnel and the rock covers everything; the same goes for the sky, which turns from day to dusk.
Engine and carriages are SVG groups drawn in code. On every frame (gsap.ticker) the distance the train has travelled against the world is worked out: the wheels turn by distance over radius, the coupling rod follows a circle, speed and acceleration come from there. Each carriage has two springs: one for the coupling, which makes it stretch and bunch up pulled by the one ahead, and one for the rocking, with a different phase per carriage; at the rail joints it bumps by a pixel.
The hill is drawn in the track layer, so behind the train; the tunnel’s two stone jambs sit in a layer in front, so the train passes under them. For each carriage the code checks whether its centre is inside the tunnel: the windows go towards yellow, a dark veil covers the paint and the glints disappear. The steam is a pool of small clouds: one leaves every half turn of the wheels, stays still in the world (it slides with the track), rises, swells and fades.
Every stop has a time window in the timeline; the ticker looks at where the timeline has got to and shows the right ticket: it comes in with back.out and the title’s words rise one by one, the old one leaves upwards. The station sign is a pendulum hanging from the world: the world’s acceleration pushes it, a spring with friction brings it back down, and when the ticket arrives it gets a little extra push.
| Name | File | Default | What it changes |
|---|---|---|---|
CARS | script.js | 3 | How many carriages the engine pulls, from 1 to 6. |
SPEED | script.js | 1 | How far apart the landscape layers move: 0 all together, 1 natural, 1.5 very deep. |
SMOKE | script.js | 1 | How much steam the engine puffs: 0 none, 2 a lot. |
SWAY | script.js | 1 | How much the carriages rock and stretch on their couplings: 0 stiff. |
SMOOTH | script.js | 1 | How many seconds the ride takes to catch up with the scroll. |
--train-body | style.css | #1f5c47 | The colour of the train’s paint (and of the signs). |
--sky-dusk-bottom | style.css | #f4a06c | The colour of the low sky at dusk, over the sea. |
For the page of a journey, an itinerary, a story in stages: an agency, a festival across several towns, a company’s history year by year, an onboarding path. Every stop is a chapter and the changing landscape keeps the scroll alive.
Same ride, same animations: the train is bigger compared with the screen and sits further right, so the carriages at the back run off the edge; the ticket takes the top and the sign hangs under it, above the train. The finger scrolls like the wheel.
The train does not travel: it is already in place and, when you reach a stop, the scene fades to the paper colour and reappears at the new station. The wheels do not turn, the carriages do not rock, no steam, the signs stay still and the tickets fade in. The windows in the tunnel still light up.
GSAP 3.15.0, Lenis 1.3.26, Fraunces (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 20 KB; the libraries: GSAP + ScrollTrigger 45 KB, Lenis 5.3 KB.
Tested on 10 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.