Baktiyar Ablaikhan

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).