
Marquee that speeds up with scroll
Scroll, Text, Hover
Free
In the minimal style: a page of thin lines with a word hidden in it, and on top a black film full of slits. The pointer slides it and turns it: askew, wide moiré bands run between the slits; level, it clicks into register and the word lights up and gains weight.
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 word under the film</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage" data-stage>
<!-- the page of fine lines with a word hidden in it, and the film of slits that reads it, on one canvas -->
<canvas class="page" data-page role="img" aria-label="A page of fine lines and a dark film of slits: held level over the page, the film shows a hidden word"></canvas>
<!-- the mark: a small grate of grey lines, and a darker one between them that turns with the film -->
<header class="mark" data-label>
<svg class="mark__grate" viewBox="0 0 16 16" aria-hidden="true">
<g class="mark__back">
<rect x="0" y="1.0" width="16" height="1"/>
<rect x="0" y="4.2" width="16" height="1"/>
<rect x="0" y="7.4" width="16" height="1"/>
<rect x="0" y="10.6" width="16" height="1"/>
<rect x="0" y="13.8" width="16" height="1"/>
</g>
<g class="mark__film" data-mark-film>
<rect x="2" y="2.6" width="12" height="1"/>
<rect x="2" y="5.8" width="12" height="1"/>
<rect x="2" y="9.0" width="12" height="1"/>
<rect x="2" y="12.2" width="12" height="1"/>
</g>
</svg>
<span class="mark__name" data-rise>Scrim</span>
</header>
<!-- the hint: what to do, and what to do once the film moves -->
<p class="hint" data-label>
<span class="hint__roll">
<span class="hint__line">Move the pointer to slide the film.</span>
<span class="hint__line">Hold it level, and the word appears.</span>
</span>
</p>
<!-- the reading: how far the film is turned, then in register -->
<p class="readout" data-label>
<span class="readout__roll">
<span class="readout__line" data-tilt>tilt 0.0°</span>
<span class="readout__line readout__line--on">in register</span>
</span>
</p>
<!-- the hidden word, for screen readers -->
<p class="sr" data-said></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>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.
18 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 word under the film” from Animorio and fit it into my siteOn a hidden canvas the script draws the lines: one every PITCH pixels, LINE times the step thick, with step and thickness rounded to the real pixels of the screen (and the step even, so half a step is a whole pixel too). Then it writes the word on a second canvas that works as a mask: with destination-out it removes the lines inside the letters, and on a third canvas, with destination-in, it keeps only inside the letters the same lines moved by half a step. The two halves together are the page, repainted only when the word changes weight.
The film is a single path of black rectangles: between one slit and the next there is a bar as wide as the step minus the line. It is drawn on every frame with translate, rotate and scale around its centre, which always sits a whole number of steps from the first line: level and at its size, every slit falls exactly on a line. Outside the letters the dark line shows, inside them the paper: the word reads light on black.
Three damped springs, worked out in steps of a hundred and twentieth of a second: the position follows the pointer sideways, the rotation follows the height of the pointer (level in the LEVEL band, up to TILT degrees at the edges), the scale follows the speed (LIFT). The moiré is not drawn: it comes by itself from the two grids. Turned by a small angle they make bands about the step divided by the angle wide; with a different scale the bands run across.
A quarter of a degree from level the rotation spring gets stiffer and nearly damped, like a detent: the film clicks to zero with one last small swing, and once it is close enough it is set exactly to zero, so the slits sit sharp on the lines. If it stays level and at its size for a moment it is in register: GSAP colours and stretches the corner marks with back.out, rolls the reading and brings the weight of the word from WEIGHT minus 300 to WEIGHT, repainting the page in steps of ten.
The entrance is a GSAP timeline: the lines are drawn from the middle row, the film comes in from the right turned and lifted (the timeline moves the targets, the springs add the lag and the settling). When nothing moves any more the canvas is not redrawn. When the pointer leaves, the targets go back to the middle and to zero; with a finger you drag, and lifting it does the same.
| Name | File | Default | What it changes |
|---|---|---|---|
WORD | script.js | 'LATENT' | The word hidden in the page. Short and in capitals works best: it reads in stripes. |
PITCH | script.js | 6 | The distance from one line of the page to the next, in pixels. On the phone it gets a little smaller by itself. |
LINE | script.js | 0.36 | How thick the lines and the slits are, as a share of PITCH: thicker, the word lights up brighter but the page is darker. |
TILT | script.js | 7 | How many degrees the film turns with the pointer at the top or bottom of the page. More degrees, narrower moiré bands. |
LEVEL | script.js | 0.24 | The band in the middle of the page where the film stays level and in register, as a share of its height. |
SWING | script.js | 0.35 | How much the film swings past level before it settles (0 = not at all). |
LIFT | script.js | 0.025 | How much the film lifts when the hand is quick: it grows a hair larger and the slits spread wider than the lines. |
WEIGHT | script.js | 760 | The weight of the word when the film is in register, from 300 to 900: out of register it is 300 lighter. |
At the top of a site that wants a quiet front page with a surprise to look for: a graphic design or print studio, a photography show, a label, a product launch that keeps its name hidden. The hidden word can be a name, a date or an invitation.
The same animation with the sizes adapted: the page takes nearly the whole width, the lines are a little closer, the word picks by itself the cut of the typeface that makes it largest and the film stays inside the screen with its corner marks. The finger is the pointer: dragging, the film slides and turns, lifting it, it goes back to the middle and into register.
Nothing slides and nothing turns: the page is already ruled and the film fades in at the middle, already in register, with the word light and the corner marks red. The pointer does not move it.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 8.5 KB; the libraries: GSAP 28 KB.
Tested on 16 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.