Text blur
- Source
- After bencho.dev ↗
- Author
- Interaction after bencho.dev
- Licence
- Original code — interaction after bencho.dev
Loading…
Words blur in, then sharpen only where your pointer is looking.
Code
"use client";
import { motion } from "motion/react";
import { useRef, useState } from "react";
import { Surface } from "../ui";
const TEXT =
"Focus is a small circle of light. Everything outside it softens, waits, and comes back the moment you look at it.";
const WORDS = TEXT.split(" ");
export default function TextBlur() {
const [round, setRound] = useState(0);
const [focus, setFocus] = useState<{ x: number; y: number } | null>(null);
const [centers, setCenters] = useState<{ x: number; y: number }[]>([]);
const [fs, setFs] = useState(16);
const refs = useRef<(HTMLSpanElement | null)[]>([]);
const box = useRef<HTMLParagraphElement>(null);
const measure = () => {
const b = box.current?.getBoundingClientRect();
if (!b || !box.current) return;
setFs(parseFloat(getComputedStyle(box.current).fontSize));
setCenters(
refs.current.map((el) => {
const r = el?.getBoundingClientRect();
return r ? { x: r.left + r.width / 2 - b.left, y: r.top + r.height / 2 - b.top } : { x: 0, y: 0 };
}),
);
};
const blurFor = (i: number) => {
if (!focus || !centers[i]) return 0;
const d = Math.hypot((centers[i].x - focus.x) * 0.8, (centers[i].y - focus.y) * 1.6);
return Math.min(7, Math.max(0, (d / fs - 2.2) * 1.1));
};
return (
<Surface>
<p
ref={box}
onPointerEnter={measure}
onPointerMove={(e) => {
const b = box.current!.getBoundingClientRect();
setFocus({ x: e.clientX - b.left, y: e.clientY - b.top });
}}
onPointerLeave={() => setFocus(null)}
onClick={() => setRound((r) => r + 1)}
className="max-w-[22em] cursor-pointer px-[1em] text-center text-[1.5em] leading-[1.35] text-[#151515]"
>
{WORDS.map((w, i) => {
const blur = blurFor(i);
return (
<span key={`${round}-${i}`}>
<motion.span
ref={(n) => void (refs.current[i] = n)}
className="inline-block"
initial={{ opacity: 0, filter: "blur(10px)", y: 6 }}
animate={{ opacity: 1 - blur * 0.07, filter: `blur(${blur}px)`, y: 0 }}
transition={{
opacity: { duration: focus ? 0.25 : 0.6, delay: focus ? 0 : i * 0.045 },
filter: { duration: focus ? 0.25 : 0.6, delay: focus ? 0 : i * 0.045 },
y: { duration: 0.6, delay: i * 0.045 },
}}
>
{w}
</motion.span>{" "}
</span>
);
})}
</p>
<p className="pointer-events-none absolute bottom-[6%] font-mono text-[0.7em] text-[#8a8984]">move to focus · click to replay</p>
</Surface>
);
}
Prompt
Build "Text blur", a text animation. What it does: Words blur in, then sharpen only where your pointer is looking. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS, using motion. Technique: an HTML canvas, the Motion library, pointer tracking. It sits on a light background, in a 16:10 frame. Respect prefers-reduced-motion with a still state, pause work when the element is off screen, and keep it accessible. Original: https://bencho.dev (Interaction after bencho.dev, Original code — interaction after bencho.dev).