← all topics

Mouse-Driven Transforms

CSS

The same 9 elements, the same CSS properties — triggered two different ways. Left: a CSS transition fires when the cursor enters each element. Right: the same scale, glow, and color continuously lerp toward targets set by global cursor distance, before you've even touched an element.

hover

fires on cursor enter/leave — binary, transition-driven

proximity

lerps from cursor distance — continuous, no hover needed

Controls
0.3s
200px
0.07

concepts

hover (binary)

CSS transition fires on mouseenter / mouseleave. The state is strictly on or off — the browser handles interpolation, but only after the cursor crosses the element boundary.

proximity (continuous)

A RAF loop reads global cursor position every frame, computes distance to each element center, and lerps a 0–1 weight. Elements react before you touch them.

distance formula

dist = √((x − cx)² + (y − cy)²). Dividing by the radius normalizes to [0, 1]: 0 means far away, 1 means directly over the element center.

why lerp instead of snap

Lerping the scale each frame (scale += (target - scale) * speed) means the element always moves a fraction toward its target — producing smooth deceleration for free.

core pattern

// Each RAF frame, for each element:
const dist = Math.sqrt((mouseX - cx)**2 + (mouseY - cy)**2);
const t    = Math.max(0, 1 - dist / radius); // 0=far, 1=center

// Lerp scale toward the proximity-weighted target
scale += (1 + t * 0.28 - scale) * 0.07;
el.style.transform = `scale(${scale})`;