
Stacking certificates
Hover, Images
NewFree
In the minimal style: a photo out of focus and a thin crosshair that follows the pointer on a spring. Round the crosshair the photo comes into focus in rings, the sharpest in the middle; when the crosshair stops, the focus goes a hair too far and comes back, the crosshair closes in by one pixel and turns orange, and the lens stops down click by click, from f/2.8 to f/8, while the number under the photo loses 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 photo brought into focus</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage" data-stage>
<header class="top">
<p class="mark">Vellum</p>
<p class="title" data-title>Still life, north light</p>
</header>
<!-- the plate: the photo on a canvas, sharp only round the crosshair -->
<figure class="plate" data-plate role="img" aria-label="Still life, north light">
<canvas class="photo" data-photo aria-hidden="true"></canvas>
<i class="tick tick--x" data-tick-x aria-hidden="true"></i>
<i class="tick tick--y" data-tick-y aria-hidden="true"></i>
<div class="cross" data-cross aria-hidden="true">
<i class="arm arm--n" data-arm></i>
<i class="arm arm--e" data-arm></i>
<i class="arm arm--s" data-arm></i>
<i class="arm arm--w" data-arm></i>
</div>
<figcaption class="read" aria-hidden="true">
<span class="stop"><span class="stop__f" data-f>f/</span><span class="stop__n" data-stop>2.8</span></span>
<span class="scale" data-scale>
<i class="scale__mark" data-scale-mark></i>
</span>
</figcaption>
</figure>
</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.
24 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 photo brought into focus” from Animorio and fit it into my siteAt the start the script draws the photo on the canvas at the size of the screen and makes five copies of it, from sharp to the most blurred (BLUR pixels), with ctx.filter = 'blur()'. Round every copy there is a frame of the photo itself, stretched, so the blur does not pull in the black of the page. No frame blurs anything: it only mixes the five copies already made.
On every frame the most blurred photo covers the whole canvas; on top, from the least sharp to the sharpest, every copy is pasted only in a circle round the crosshair, cut out by a radial gradient in destination-in that softens the edge. The circles are multiples of the sharp circle (RINGS): the blur falls away in rings towards the middle. It redraws only when the crosshair or the circle have changed.
The crosshair does not jump to the pointer: it gets there on a real spring (SPRING, stiffness and damping), worked out in small steps inside GSAP's ticker. With the damping a little under critical it arrives, goes a pixel or two past and settles. The four lines are one-pixel divs; GSAP does not touch them, the ticker writes their position.
The sharp circle depends on two numbers: q, how much it is in focus, and s, the aperture. When the crosshair stops (or reaches a spot on its round) a timeline takes q to 1.08, then to 0.97 and finally to 1: the focus goes past and comes back. Meanwhile pinch brings the lines one pixel closer with elastic.out and the colour turns to the accent; then s goes up one at a time with back.out, a click every 0.27 s, up to REST_STOP. The circle grows by 2 to the s/4: a quarter of a doubling with every click.
The number under the photo is in Roboto Flex, a variable typeface. The ticker writes font-variation-settings on every frame: wght goes from 780 at f/2.8 to 230 at f/16, following s. When the aperture changes the text changes and wdth starts at 126 and comes back to 100 with an elastic. While the crosshair runs, q drops to WIDE and s goes back to zero: the number counts back down to 2.8 and turns heavy again.
| Name | File | Default | What it changes |
|---|---|---|---|
IMAGE | script.js | '' | The photo: an address, or a file loaded in the Playground. Empty, the script draws the photo. The frame takes the photo's proportions. |
BLUR | script.js | 14 | How blurred the photo is far from the crosshair, in pixels. |
FOCUS | script.js | 0.13 | The sharp circle with the lens wide open (f/2.8), as a part of the photo's short side. It grows as the lens stops down. |
REST_STOP | script.js | 8 | The aperture the lens stops down to when the crosshair rests: 2.8, 4, 5.6, 8, 11 or 16. Higher means more clicks and more of the photo in focus. |
SPRING | script.js | [170, 22] | The crosshair's spring: how stiff it is and how much it is held back (less damping, more wobble). |
POINTS | script.js | [[0.56, 0.64], [0.75, 0.58], [0.34, 0.38], [0.2, 0.55]] | The spots the crosshair visits by itself, as parts of the photo's width and height. With your own photo, move them onto its subjects. |
HOLD | script.js | 1.2 | How many seconds the crosshair stays on each spot, in focus. |
TOUR | script.js | true | Whether the crosshair goes round by itself when nobody points. With false it stays on the first spot until the mouse or a finger comes. |
For a photo to be looked at slowly: the opening of a photographer's portfolio, a product page whose details you discover, the cover of a photo story, the photo of a dish or a hotel room, the site of a camera shop or a photography course. It works with any photo, including one loaded in the Playground.
The same animation with the sizes adapted: the photo is almost as wide as the screen and cropped a little at the sides, the crosshair is smaller and follows the finger; four seconds after the finger lifts, the crosshair goes back to its round.
Nothing moves: the photo fades in already in focus on the first spot, at f/8, with the orange crosshair. A click or a tap moves the focus at once, with no spring and no round.
GSAP 3.15.0, Roboto Flex (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 11 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.