
An interactive website responds continuously to the visitor: eyes that follow the cursor, a product you drag round, a book you pull off a shelf, a car you drive. The good ones share four values. They react within one frame, ease toward the target over 80ms to 250ms, ignore movements inside a dead zone of about 8px, and keep working on a touch screen with no cursor at all.
Every one of these effects is a loop with a target, which is why the same few lines of code sit under most of the examples. The pointer sets where something wants to be, an animation frame moves it part of the way there, and the loop repeats until it arrives.
Below are interactive website examples grouped by what you do with them (follow, drag or turn), then the mechanics in code and the fallbacks that keep them working on a phone. The designs come from the Thanor library, where Vesperion, the first cursor example, is free with an account.
What makes an interaction feel alive, in values#
Interaction design on the web comes down to a handful of numbers. Too little lag feels glued on, too much feels broken, and no limits make a character look possessed.
| Value | Typical range | What it controls |
|---|---|---|
| Response | the next animation frame | the first movement after input, 16.7ms at 60Hz |
| Time constant | 15ms to 300ms | how heavy the follower feels |
| Dead zone | about 8px around the pivot | stops jitter when the pointer sits on the target |
| Travel | pupils 6px to 10px, heads about ±70°, tilts 6° to 10° | how far anything is allowed to go |
| Idle | hold the last position | what happens when the pointer stops |
| Fallback | drag, tap, loop or still | what touch, keyboard and reduced motion get |
Two of those deserve a sentence each. The time constant is the single biggest lever on feel: a custom cursor dot near 15ms looks attached, eyes at 80ms to 120ms look alive, and a heavy object or parallax layer at 250ms looks like it has mass. And the idle rule matters more than it seems. A character that snaps back to center the moment the pointer stops looks mechanical, so the best examples hold where they were.
Interaction differs from scroll animation in one way that shapes all the code below. A scroll animation maps one input to one timeline, so it can be scrubbed. An interaction has to chase a target that can move anywhere at any time, which is why it needs easing toward a goal instead of a fixed curve.
Cursor-follow examples#
The most common interactive effect is something that looks at you or chases your pointer. Vesperion is a designer's portfolio hero: a small creature in a diving suit stands in front of a huge white word in a cavern, and its eyes follow your cursor. No code moves the eyes. The cursor chooses which recorded frame of a short film is shown, the one in which the pupils already point your way, and the film is encoded with every frame as a keyframe so each jump lands at once. A second layer keeps the creature in front of the word. Woolgather uses the same mechanism in a footer.
Strix turns a whole head. A snowy owl in a hi-vis jacket follows the pointer across the page through one film of a head turn, scrubbed by the pointer's horizontal position: point at the gate and it looks down at it, point at the right edge and it looks over its shoulder. The pair of eyes in its headline follows too.
Pounce inverts the idea. You steer a red laser dot, and a ginger kitten turns her whole head after it. The dot chases the pointer about one frame behind, and her head picks a moment of a pre-rendered film by the dot's direction, taking the shorter way round.
Free with an account. A diver's eyes follow your cursor because the cursor picks which recorded frame is shown.
Open the prompt →A snowy owl's head turns to follow the pointer across the page, and the drop opens only with the word.
Open the prompt →A ginger kitten turns her whole head after the red laser dot you steer, the dot one frame behind the pointer.
Open the prompt →How cursor follow works: pointer events, a unit vector and rAF#
Strip away the art and every cursor follower runs the same six steps:
- Listen for
pointermoveon the window, passive, and readclientXandclientY. SkippointerType === "touch"; touch gets its own behavior. - Take the vector from the pivot, such as the point between the eyes, to the pointer:
dx,dy, and its length fromMath.hypot(dx, dy). - Ignore it inside the dead zone, where the angle is unstable and jitters.
- Divide by the length to get a unit vector, the pure direction, and scale it by how far the pointer is (capped at
1) and by the maximum travel. - Store that as the target and start one
requestAnimationFrameloop if none is running. - In the loop, move the current value part of the way to the target, write one
transform, and stop the loop once it has arrived.
const eyes = document.querySelector(".eyes");
const pupils = document.querySelector(".pupils");
const pivot = { x: 0, y: 0 };
const target = { x: 0, y: 0 };
const current = { x: 0, y: 0 };
let frame = 0, last = 0;
function measure() {
const r = eyes.getBoundingClientRect();
pivot.x = r.left + r.width / 2;
pivot.y = r.top + r.height / 2;
}
measure();
addEventListener("resize", measure, { passive: true });
addEventListener("scroll", measure, { passive: true });
addEventListener("pointermove", (e) => {
if (e.pointerType === "touch") return;
const dx = e.clientX - pivot.x, dy = e.clientY - pivot.y;
const len = Math.hypot(dx, dy);
if (len < 8) return; // dead zone
const reach = Math.min(len / 400, 1); // 0 near, 1 far
target.x = (dx / len) * reach * 7; // unit vector x travel
target.y = (dy / len) * reach * 7;
if (!frame) { last = performance.now(); frame = requestAnimationFrame(tick); }
}, { passive: true });
function tick(now) {
const dt = Math.min(Math.max(now - last, 0), 50); last = now;
const k = 1 - Math.exp(-dt / 90); // 90ms time constant
current.x += (target.x - current.x) * k;
current.y += (target.y - current.y) * k;
pupils.style.transform = `translate(${current.x}px, ${current.y}px)`;
const settled = Math.abs(target.x - current.x) + Math.abs(target.y - current.y) < 0.01;
frame = settled ? 0 : requestAnimationFrame(tick);
}The pivot is measured on load, resize and scroll rather than on every pointer event, because reading layout sixty times a second for a value that rarely changes is wasted work. For a head or a film that maps direction to a frame, replace the unit vector with Math.atan2(dy, dx), which gives the angle directly.
Easing toward the target, frame-rate independent#
The line that decides how the follower feels is k = 1 - Math.exp(-dt / tau). Each frame closes the fraction k of the remaining gap, and because k depends on the real time since the last frame, the motion is identical on a 60Hz and a 120Hz screen. After one time constant the follower has covered 63% of the gap, after three it has covered 95%.
The common shortcut, current += (target - current) * 0.1, ties the speed to the frame rate. At 60Hz it closes 10% of the gap every 16.7ms; at 120Hz it runs twice per 16.7ms and closes 19%, so the same page feels nearly twice as fast on a newer phone or monitor. Use the exponential form and clamp dt, so a frame after a background tab does not jump the whole distance at once.
Time constant tau | 95% settled | Feels like |
|---|---|---|
15ms | 45ms | a custom cursor dot, attached |
90ms | 270ms | eyes or pupils, alive |
150ms | 450ms | a head turning |
250ms | 750ms | a heavy object, a parallax layer |
Exponential easing never overshoots, which suits eyes and cursors. For a playful object, a damped spring adds a small overshoot and settle; a critically damped one, the kind Pounce uses for its kitten's head, arrives as fast as possible without overshooting. The motion values guide covers curves for the parts of a page that play on their own.
Drag and turn examples#
The second family lets the visitor take hold of something.
Kickflip is a bubblegum pink 3D skate shop. A wheel flick, a click or a swipe sends the poster's type flying past and swings the camera to face a rack, and at the rack the deck under your pointer turns from side-on to face you, showing its full graphic. On touch, a tap turns the nearest deck and a drag along the row turns the deck under your finger. Octavo is a small press's shelf of twelve painted hardbacks: a click on a spine, the arrows, a drag, the wheel or the arrow keys glide the shelf on a spring, the book in front leans out and turns to show its cover, and a tap opens it. Grumps puts four vinyl toys on a ring that the arrows, a swipe, the keyboard or a dot turn one slot round, all on one 650ms clock.
A pink 3D skate shop where the deck under your pointer turns to face you; on touch, a tap does the same.
Open the prompt →Pull a hardback off a sunlit shelf by click, drag, wheel or arrow keys, and it turns to show its cover.
Open the prompt →Four vinyl toys on a ring you turn with arrows, a swipe, the keyboard or the dots, on one 650ms clock.
Open the prompt →Two public sites show the idea at the scale of a world. Bruno Simon's portfolio is a physics game in which you drive a small vehicle around a stylized island, and its case study on Awwwards says it runs on WebGPU when available and switches to a lower quality preset on mobile. Abeto's Messenger is a browser game in which you deliver mail around a very small planet.
The mechanics under a drag are short. Call setPointerCapture on pointerdown so the drag continues outside the element, wait for about 5px of travel before treating a press as a drag so clicks still work, and on release snap to the nearest item with the same easing. The best 3D websites and the Three.js examples go further into scenes.
Touch, keyboard and reduced motion fallbacks#
A touch screen has no hover and a keyboard has no pointer, so each effect needs a stated fallback. Detect fine pointers with matchMedia("(hover: hover) and (pointer: fine)") and listen for changes, since a laptop with a touch screen can switch. The examples above use four patterns:
- Drag to steer. On touch, dragging sideways turns Judgy's alpaca toward your finger; she holds the angle briefly when you let go, and after a few seconds idle she looks around on her own.
- Tap to place. In Pounce a touch puts the laser dot under your finger and a drag steers it.
- A loop instead. Vesperion and Woolgather stop tracking at phone widths and simply play their film as a muted loop.
- A still frame. Under
prefers-reduced-motion: reduce, the characters become posters, and anything that changes state, like Strix's gate, still works without the motion.
On a horizontal drag surface, set touch-action: pan-y so a vertical swipe still scrolls the page. According to MDN, when the browser takes over a gesture it fires pointercancel, so reset any drag state there as well as on pointerup.
Keyboards need real controls: arrow keys on a shelf or ring, focusable buttons with a visible focus ring, and a target for followers when a control has focus. WCAG 2.5.7 asks that anything done by dragging can also be done with a single pointer without dragging, which arrow buttons and taps satisfy.
How to prompt an interactive website with AI#
A model asked for "eyes that follow the cursor" often writes a mousemove handler that sets the position directly, with no easing, no dead zone, no touch path and a layout read on every event. The prompt has to name the mechanics, because those are the decisions the effect is made of:
Hero with a character whose pupils follow the cursor.
Input: pointermove on window, passive; mouse and pen only.
Pivot: midpoint between the eyes, measured on load, resize, scroll.
Dead zone 8px. Direction: unit vector (dx / len, dy / len);
reach = min(len / 400, 1); pupils travel at most 7px.
One requestAnimationFrame loop, k = 1 - exp(-dt / 90ms),
dt clamped to 50ms; stop the loop when settled.
Touch, (hover: none): pupils drift on a 6s loop; a horizontal drag
turns them; touch-action: pan-y.
Reduced motion: pupils centered, no loop.
Keyboard focus on a button: pupils look at the focused button.The same structure works for a drag: name the capture, the threshold, the snap and the keyboard equivalent. Website animation effects lists the other effects worth specifying this way, and building a 3D site with Claude covers scenes. If you work in Claude or Cursor, the Thanor MCP pulls a design's complete mechanics, the input, the easing and the fallbacks, straight into the conversation.
Interactive website mistakes to avoid#
- Mouse only. A hover effect with no touch path is invisible on every phone.
- A fixed lerp per frame, which runs nearly twice as fast on a
120Hzscreen. - Reading layout on every pointer event. Measure on resize and scroll.
- No dead zone, so the follower twitches when the pointer rests on it.
- Unclamped travel, so pupils leave the eye and heads spin.
- Framework state on every pointer move. Keep moving values in a ref or a closure, or the component re-renders sixty times a second.
- Dragging as the only way. Arrows and taps make it usable for everyone.
- A custom cursor on touch screens, where there is nothing to draw.
The interactions on SaaS heroes and 3D portfolios follow the same rules, with the character or object changed.
Where to go from here#
Open Vesperion and move your cursor around the creature. It is free with an account, so you can copy its prompt and read exactly how the frame-picking gaze, the depth layer and the phone fallback are specified, then paste it into Claude, ChatGPT, Cursor or Lovable and build it.
The rest of the Thanor library holds the other examples on this page, each with a prompt that carries its interaction as values rather than adjectives.
Questions this raises
What is an interactive website?
An interactive website responds continuously to what the visitor does: elements that follow the cursor, objects you drag or turn, scenes you steer, or choices that change the page. It differs from a scroll animation, which plays along a fixed timeline as you scroll. Good interactive sites react within one frame, ease toward the target over 80 to 250 milliseconds, and still work on touch screens and keyboards.
How do you make an element follow the cursor?
Listen for pointermove, take the vector from the element to the pointer, divide it by its length to get a unit vector, and scale that by how far the element may travel. Then move the element toward that target inside a requestAnimationFrame loop, easing by one minus e to the power of minus dt over a time constant, so the motion is the same at 60Hz and 120Hz.
How do interactive websites work on mobile?
A touch screen has no hover, so every cursor effect needs a fallback: a drag that steers it, a tap that places the target, an idle animation or a still frame. Detect fine pointers with the media query (hover: hover) and (pointer: fine), set touch-action: pan-y so vertical scrolling still works, and give every drag a tap or button equivalent.
What is an interactive 3D website?
A page where a 3D scene responds to the visitor, rendered with WebGL or WebGPU, usually through Three.js. You might drive a vehicle around a world, as on Bruno Simon's portfolio, turn a product to face you, or walk around a shop. These pages need a loader, a lighter quality preset for phones and a fallback for devices that cannot run the scene.
Part of
Inspiration, with the prompt
Examples of the looks and effects people search for, from 3D scenes to scroll animation, each one shown working and paired with the specification that builds it.
