
Marquee that speeds up with scroll
Scroll, Text, Hover
Free
A word inside a selection frame, like a layer in a design tool: the four handles work, and pulling a corner sets the word again to fill the frame, using the font’s width axis.
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 with handles</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/anybody@5.3.0/wdth.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist-mono@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the board is one screen: two rulers, the word inside its selection frame, a colleague's cursor that types -->
<main class="board" data-board>
<div class="ruler ruler--x" data-ruler="x" aria-hidden="true">
<span class="ruler__band"></span>
<span class="ruler__numbers" data-numbers="x"></span>
<span class="ruler__mark" data-mark="x"></span>
</div>
<div class="ruler ruler--y" data-ruler="y" aria-hidden="true">
<span class="ruler__band"></span>
<span class="ruler__numbers" data-numbers="y"></span>
<span class="ruler__mark" data-mark="y"></span>
</div>
<span class="ruler__corner" aria-hidden="true"></span>
<header class="top">
<p class="brand">Offcut</p>
<p class="top__line">A small studio for printed things</p>
</header>
<!-- dashed guides along the frame's edges, shown while a corner is being pulled -->
<div class="guides" aria-hidden="true"><i class="guide guide--left"></i><i class="guide guide--right"></i><i class="guide guide--top"></i><i class="guide guide--bottom"></i></div>
<div class="frame" data-frame>
<h1 class="word" data-word aria-label="Offcut"><span class="word__letter" aria-hidden="true">O</span><span class="word__letter" aria-hidden="true">F</span><span class="word__letter" aria-hidden="true">F</span><span class="word__letter" aria-hidden="true">C</span><span class="word__letter" aria-hidden="true">U</span><span class="word__letter" aria-hidden="true">T</span></h1>
<span class="frame__edge frame__edge--top" aria-hidden="true"></span>
<span class="frame__edge frame__edge--right" aria-hidden="true"></span>
<span class="frame__edge frame__edge--bottom" aria-hidden="true"></span>
<span class="frame__edge frame__edge--left" aria-hidden="true"></span>
<button class="handle handle--nw" type="button" data-handle="nw" aria-label="Top left corner: drag, or use the arrow keys, to resize the word"></button>
<button class="handle handle--ne" type="button" data-handle="ne" aria-label="Top right corner: drag, or use the arrow keys, to resize the word"></button>
<button class="handle handle--sw" type="button" data-handle="sw" aria-label="Bottom left corner: drag, or use the arrow keys, to resize the word"></button>
<button class="handle handle--se" type="button" data-handle="se" aria-label="Bottom right corner: drag, or use the arrow keys, to resize the word"></button>
<p class="frame__size" data-size aria-live="off"><span data-size-box>0 × 0</span><span class="frame__axis" data-size-axis>width 100</span></p>
</div>
<!-- a colleague: the cursor, the name and the bubble where the greeting is typed -->
<div class="peer" data-peer aria-hidden="true">
<svg class="peer__arrow" viewBox="0 0 24 26" width="24" height="26"><path d="M2 2 L2 21 L7.4 16.2 L11 24 L14.6 22.4 L11.2 14.8 L18.6 14.4 Z"/></svg>
<span class="peer__name">Ines</span>
<span class="peer__bubble" data-bubble><span data-typed></span><i class="peer__caret" data-caret></i></span>
</div>
<p class="hint" data-hint>Pull a corner: the word is set again to fill its frame.</p>
<button class="replay" type="button" data-replay data-autoclic>Replay</button>
</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.
36 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 with handles” from Animorio and fit it into my siteOnce the font is in, the script takes three measures on a canvas: how tall the capitals are, how far the font rises and how far it drops, all as shares of the type size. Then it sets the word at 200 px and reads its width at every step of the wdth axis, from 50 to 150 every 10: eleven measures, in ems, good for any size.
The frame’s height, less a little air above and below, is how tall the capitals must be: the type size is that height divided by the share of the size the capitals take. The line is as tall as the capitals, and a shift downwards (half of: capitals, minus ascent, plus descent) sets them exactly inside it.
The width that is left, divided by the type size, says how many ems wide the word must be. Among the eleven measures the script finds the two that hold it and interpolates the font-stretch value. If the frame asks for less than wdth 50 or more than wdth 150 the axis stops there and a scaleX makes up the rest: “stretched” shows under the frame.
Each handle is a button. On pointerdown it captures the pointer and keeps the distance between the point touched and the corner; while dragging, that corner follows and the opposite one stays put, between a minimum of 150 by 64 px and the edges of the board. The arrow keys move it by 10 px, 40 with Shift. On release the letters settle with an elastic; two clicks on the frame put it back as it was.
Four CSS variables on the board, --ox, --oy, --fw and --fh, say where the frame is: the frame itself, the rulers and the dashed guides all read them. The ticks are three gradients repeated every 10, 50 and 100 px, moved with background-position so that zero falls on the frame’s corner; a yellow band covers its width and height, and a red line follows the pointer with gsap.quickTo, in 0.2 seconds.
One timeline. The greeting is typed in a second, by a counter that slices the sentence, and the text cursor blinks in steps; the letters start 40 px to the right, at scale 0.9, 0.03 seconds apart, on a curve that is the solution of a spring (stiffness 400, damping 40, mass 4); then lines, handles, measurements. The colleague pulls the corner by calling the same function the handles use. Touch a handle before it ends and everything stops: you are in charge.
| Name | File | Default | What it changes |
|---|---|---|---|
WEIGHT | script.js | 780 | The word’s weight. |
AXIS | script.js | [50, 150] | The two ends of the width axis: change them if you change the font. |
PAD | script.js | [0.07, 0.13] | The air between word and frame, as shares of the frame’s height: at the sides, and above and below. |
MIN | script.js | [150, 64] | The smallest frame, in pixels. |
GREETING, TYPE_TIME | script.js | 'hello, we are', 1 | What the colleague types, and in how many seconds. |
LETTER_GAP, SPRING | script.js | 0.03, 400 / 40 / 4 | The seconds between one letter and the next, and the spring they arrive on: stiffness, damping, mass. |
KEY_STEP | script.js | 10 | The pixels per arrow key (four times as much with Shift). |
--blue, --yellow | style.css | #2343e6, #ffd21f | The board’s colour, and that of the frame, bands and measurements. |
For the name of a studio, a product or a person at the top of a page about design: a portfolio, an agency, a foundry, the page of a variable font.
One ruler only, at the top. The frame starts taller, with the word narrow, and the corners are dragged with a finger: around each handle there is a 46 px area. The colleague pulls less to the right, because there is little room.
No opening: word, frame and measurements are there at once, the colleague does not appear and the letters do not bounce. The handles work, and the button at the bottom puts the frame back as it was.
GSAP 3.15.0, Anybody (font variabile) 5.3.0, Geist Mono (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 9.5 KB; the libraries: GSAP 28 KB.
Tested on 30 September 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.