
Standing letters with a long shadow
Text, Scroll, 3D
NewFree
Scroll inside the preview
A minimal-style scroll animation: a road seen from the driver’s seat, made only of lines, running towards you as fast as you scroll, with bends coming in from the horizon. At the bottom the speed, written large, widens and gains weight as you speed up; at the end the road brakes and a sign drawn in a single line says where you have arrived.
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 road that runs as you scroll</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- a made-up guide to slow roads, Verge. The stage sticks while you scroll: a road seen from the driver's seat, made
only of lines, runs towards you as fast as you scroll; the speed is written at the bottom and grows wider and
bolder as you speed up. At the end of the section you arrive, and a sign drawn in one line says where. -->
<main>
<section class="corsa" data-corsa>
<div class="palco" data-palco>
<canvas class="strada" data-strada aria-hidden="true"></canvas>
<header class="testa">
<p class="marchio" data-entra>Verge</p>
<p class="testa__nota" data-entra>Slow roads, one at a time</p>
</header>
<div class="cartello" data-cartello aria-hidden="true">
<svg class="cartello__segno" viewBox="0 0 120 150" preserveAspectRatio="xMidYMax meet">
<path data-cartello-filo d="M60 150V62H8V8h104v54H60"/>
</svg>
<p class="cartello__nome" data-cartello-nome>Lower Harlow</p>
</div>
<p class="arrivo" data-arrivo>You have arrived.</p>
<div class="tachimetro" data-tachimetro>
<p class="tachimetro__numero"><span data-numero>0</span><span class="tachimetro__unita">km/h</span></p>
<span class="tachimetro__riga" aria-hidden="true"><span class="tachimetro__pieno" data-pieno></span></span>
</div>
<p class="cenno" data-entra><span class="cenno__dentro" data-cenno><span>Scroll to drive</span><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 3v13M4.5 10.5 10 16l5.5-5.5"/></svg></span></p>
</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.
21 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 road that runs as you scroll” from Animorio and fit it into my siteThe road is a canvas redrawn on every frame. In front of the camera there are 160 depth steps, longer with distance (z grows geometrically), and for each one the script knows how far the middle of the road is shifted sideways. A point of the world (x, height, z) goes on screen by dividing by z: the screen x is half the width plus f times x over z, the y is the horizon plus f times the camera height minus the point’s, over z. The camera is placed so that the carriageway, at the bottom edge of the screen, is as wide as the screen.
The bends are a fixed list: where they start on the road, how long they are and how much they turn. At every step the curvature of the stretch of road being looked at is added to the heading, and the heading to the sideways shift, like a car following the road: so a far bend bends the edges near the horizon and, as you scroll, comes closer until it passes under you. Every bend starts and ends gently (sine squared), so the edges never make a corner.
A ScrollTrigger on the section gives the scroll point from 0 to 1, and a function turns it into metres of road: still for the first four hundredths, then at a steady pace, and in the last stretch a parabola that brakes down to zero with the same slope where the two parts meet. The dashes of the centre line and the posts sit at fixed points of the road, every 12 and every 40 metres: it is the position that changes, so they run towards you by themselves, faster the faster you scroll, and every ride is the same.
The speed is real: the metres travelled in a frame divided by the frame’s time, against a reference speed (100 km/h means the whole road in CRUISE seconds). An exponential average smooths it over SMOOTH seconds, so the number rises and falls without shaking. The whole number only changes when it really changes; the typeface’s axes follow the speed, wdth from 62 to 125 and wght from 300 to 820, and the line underneath fills with scaleX.
After the braking, a timeline tied to the scroll draws the sign with stroke-dashoffset on a single-line path (post and board), brings the name up from wide and light to narrow and full and lights up the arrival line. On arrival the view nods: the horizon drops a few pixels and comes back with elastic.out. With reduced motion there is no Lenis and the road stays still; the number still tells the scroll’s speed, without changing shape, and the sign fades in.
| Name | File | Default | What it changes |
|---|---|---|---|
PLACE | script.js | 'Lower Harlow' | The name of the place you arrive at, written on the sign at the end. |
MAX_KMH | script.js | 130 | The speed that fills the speedometer’s line, gives the number its full width and weight and lights up the centre line. |
CRUISE | script.js | 8 | How many seconds the whole road takes at 100 km/h: the higher, the faster the speedometer reads for the same scroll. |
DASH_EVERY | script.js | 12 | Every how many metres a dash of the centre line starts. |
LANE_DASH | script.js | 0.45 | How long a dash of the centre line is, as a share of that distance. |
CURVES | script.js | 1 | How tight the bends are: 0 is a straight road, 2 twice as tight. |
POSTS | script.js | true | The posts at the sides of the road. |
LINE | script.js | 1.5 | The thickness of the edges and the posts, in pixels. |
SMOOTH | script.js | 0.35 | How many seconds the speedometer takes to follow the real speed: the higher, the calmer. |
RIDE | script.js | 1 | How much scroll the ride takes: 2 means twice as much. |
For the page of a trip, a car rental, a driving school, a car brand or a road guide: a scroll story that makes you feel the road and the speed without drawing a car.
The same animation with adapted sizes: the carriageway is a little wider than the screen, the horizon a little lower, the number smaller; you drive with your finger as with the mouse.
No smoothed scrolling and no running: the road stays still with its far bends, the number tells the scroll’s speed without changing shape and at the end the sign fades in.
GSAP 3.15.0, ScrollTrigger 3.15.0, Lenis 1.3.26, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 7.6 KB; the libraries: GSAP + ScrollTrigger 45 KB, Lenis 5.3 KB.
Tested on 21 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.