Proximity
I came across this tweet of a dock where the icons swelled based on how close the cursor was and I wanted to take that proximity idea further.
It's a small yet powerful distinction: hover is binary, you're either on an element or you're not. Proximity is continuous, as the cursor gets close, nearby elements can subtly scale and darken based on distance, which makes an interface feel responsive and alive.
I started where the tweet did, with a dock. Run your cursor across it and every tile reacts to how near you are.
The core is a falloff function, distance in and 0 to 1 weight out. From there I can map that weight onto whatever I like, scale, opacity, or colour.
onpointermove = (e) => {
document.querySelectorAll(".dock > *").forEach((el) => {
const r = el.getBoundingClientRect();
const d = Math.abs(e.clientX - r.x - r.width / 2);
const t = Math.max(0, 1 - d / 120); // 1 = on it, 0 = far away
el.style.scale = 1 + t * 0.5;
});
};That snippet calls getBoundingClientRect() for every element on every pointer move, a layout read per tile, per frame. So I measure once instead, cache the rectangles in an array, and only re-measure when the window resizes. Each frame then reads a single rect and runs pure maths off the cache.
const dock = document.querySelector(".dock");
let rects = [];
// Measure once. Re-measure only when layout can actually change.
const measure = () => {
const base = dock.getBoundingClientRect();
rects = [...dock.children].map((el) => {
const r = el.getBoundingClientRect();
return { el, cx: r.left - base.left + r.width / 2 };
});
};
measure();
addEventListener("resize", measure);
// Hot path: one rect read, then cached math.
dock.onpointermove = (e) => {
const x = e.clientX - dock.getBoundingClientRect().left;
for (const { el, cx } of rects) {
const t = Math.max(0, 1 - Math.abs(x - cx) / 120);
el.style.scale = 1 + t * 0.5;
}
};Same effect, and N reads per frame collapses to one. Here they are side by side, flip the strategy and watch the live counter.
A few more things to do with it
Once I had the falloff weight I could spend it on anything. Here are three variations.
Radial fields. I dropped the horizontal-only constraint and used true 2D distance, so the whole field around the cursor responds.
Magnetism. Spending the weight on position instead makes elements drift toward the cursor as it gets near. Lovely on nav items and primary actions.
Light. Spending it on luminance gives a soft pool that follows the cursor, with nearby cards lifting and their edges catching it.
One distance-to-weight function, cached rects, and direct style writes. That's all of it, and it costs almost nothing per frame.
Posted on 31 May 2026
Permalink, All Playground