A strip of stamps glides under a round lens: outside it is blurred and dim, inside the lens it is sharp, bright and larger. It slows down under the mouse, and it can be dragged and thrown.
Hover, ImagesGSAP, CSS onlyMediumLifetime
The code
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN.
On a dark green album page, a glassine strip carries a row of drawn stamps: a lighthouse, a sunflower, a comet, a whale and eight more. The row glides left by itself, blurred and almost colourless. In the middle sits a round lens with a brass rim: whatever passes under it is sharp, bright and a little larger, and under the lens you read the name and value of the current stamp, which changes with a small jump. With the mouse over it the strip slows almost to a stop; dragging moves it, and letting go with a quick gesture it glides on and then resumes its pace. With focus and the arrow keys you move one stamp at a time.
How it works
Two identical strips
The strip exists twice, with the same stamps in the same order: one behind, full width, with filter: blur(3px) brightness(0.5) saturate(0.55); one inside the lens, a circle made with clip-path: circle(50%), sharp. The same number moves both, so they cannot fall out of step.
Enlarging around a point
A point of the strip that sits at x behind must sit at c + s × (x − c) inside the lens, where c is the lens centre and s the magnification, 1.18. With transform-origin at the top left one transform is enough: translate(r + s × (x − c), r + s × (top − cy)) scale(s), where r is the lens radius.
An endless loop
One loop is the whole set: twelve stamps times their pitch. The strip holds as many copies as it takes to cover the screen, plus one, and the drawn position is x − ⌈x / loop⌉ × loop, always between minus one loop and zero: the number grows without limit, the strip does not.
Slowing, dragging, throwing
Every frame the speed chases its target with 1 − e^(−2.4 × dt): 46 pixels a second, 9 with the mouse over it. Dragging measures the speed of the gesture’s last tenth of a second; letting go, the strip starts from it and returns to its pace by itself, with no hand-written curve. The stamp under the lens is round((c − x − half a stamp) / pitch), modulo twelve.
Values to change
Name
File
Default
What it changes
FRANCOBOLLI
script.js
12 francobolli
Name, value and drawing of each stamp: the drawing is a piece of SVG in an 84 × 94 frame, inside a 100 × 125 sheet that is already perforated.
PASSO, LENTO
script.js
46, 9
Pixels per second of the strip, on its own and with the mouse over it.
INGRANDISCE
script.js
1.18
How much the lens enlarges: with the lens one and a half times as wide as the stamp, a whole stamp fits inside up to about 1.2.
--fb
style.css
clamp(128px, 24vh, 232px)
The height of a stamp: strip, lens and caption follow from it.
When to use it
For a collection to browse without hurry: products, covers, labels, works. The lens says where to look and the strip hints that there are more.
On phones
Smaller stamps and lens; a sideways finger drags and throws the strip, a vertical one scrolls the page.
With reduced motion
The strip stands still, with the first stamp under the lens: it only moves by dragging or with the arrow keys, without inertia and without the name’s jump.
Pitfalls
The blurred strip must be separate from the lens: with backdrop-filter the blur is redone every frame over the whole band, with filter on the moving strip the browser keeps it in its layer.
Inside the lens you need a solid background: between one stamp and the next you would see the blurred strip behind.
Do not reset the number at every loop: letting it grow and wrapping only what is drawn avoids jumps during a GSAP tween on the arrow keys.
Measure the throw speed on the events’ own times (event.timeStamp), as the movement of the last tenth of a second divided by its time: dividing event by event, two events in the same frame look instantaneous and the throw explodes. A finger held still for more than a tenth of a second does not throw.
Libraries
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Weight: 21 KB of our own files.
Testing
Tested on 27 September 2026
Desktop: Done
Phone: Done
Reduced motion: Done
Safari on iPhone: Done
License
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.