NamelessUI

Scrambled Text

Detects cursor position and applies a distortion effect to text.

Nameless UI - Find the perfect React UI component in seconds, build beautiful interfaces faster

import ScrambledText from "@/components/core/scrambled-text";

export default function ScrambledTextExample() {
  return (
    <ScrambledText
      style={{ color: "currentColor" }}
      radius={50}
      duration={1.2}
      speed={0.5}
      scrambleChars={".:"}
    >
      Nameless UI - Find the perfect React UI component in seconds, build
      beautiful interfaces faster
    </ScrambledText>
  );
}

Installation

Install the following dependencies:
npm install gsap
Copy and paste the following code into your project:
"use client";

import React, { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";
import { ScrambleTextPlugin } from "gsap/ScrambleTextPlugin";

gsap.registerPlugin(SplitText, ScrambleTextPlugin);

export interface ScrambledTextProps {
  radius?: number;
  duration?: number;
  speed?: number;
  scrambleChars?: string;
  className?: string;
  style?: React.CSSProperties;
  children: React.ReactNode;
}

const ScrambledText: React.FC<ScrambledTextProps> = ({
  radius = 100,
  duration = 1.2,
  speed = 0.5,
  scrambleChars = ".:",
  className = "",
  style = {},
  children,
}) => {
  const rootRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    if (!rootRef.current) return;

    const split = SplitText.create(rootRef.current.querySelector("p"), {
      type: "chars",
      charsClass: "inline-block will-change-transform",
    });

    split.chars.forEach((el) => {
      const c = el as HTMLElement;
      gsap.set(c, { attr: { "data-content": c.innerHTML } });
    });

    const handleMove = (e: PointerEvent) => {
      split.chars.forEach((el) => {
        const c = el as HTMLElement;
        const { left, top, width, height } = c.getBoundingClientRect();
        const dx = e.clientX - (left + width / 2);
        const dy = e.clientY - (top + height / 2);
        const dist = Math.hypot(dx, dy);

        if (dist < radius) {
          gsap.to(c, {
            overwrite: true,
            duration: duration * (1 - dist / radius),
            scrambleText: {
              text: c.dataset.content || "",
              chars: scrambleChars,
              speed,
            },
            ease: "none",
          });
        }
      });
    };

    const el = rootRef.current;
    el.addEventListener("pointermove", handleMove);

    return () => {
      el.removeEventListener("pointermove", handleMove);
      split.revert();
    };
  }, [radius, duration, speed, scrambleChars]);

  return (
    <div
      ref={rootRef}
      className={`m-[7vw] max-w-[800px] font-mono text-[clamp(14px,4vw,32px)] text-white ${className}`}
      style={style}
    >
      <p>{children}</p>
    </div>
  );
};

export default ScrambledText;
Update the import paths to match your project setup.

Usage

import ScrambledText from "@/components/core/scrambled-text";

export default function ScrambledTextExample() {
  return (
    <ScrambledText
      style={{ color: "currentColor" }}
      radius={50}
      duration={1.2}
      speed={0.5}
      scrambleChars={".:"}
    >
      Nameless UI - Find the perfect React UI component in seconds, build
      beautiful interfaces faster
    </ScrambledText>
  );
}

Props

PropTypeDefault
radius?
number
100
duration?
number
1.2
speed?
number
0.5
scrambleChars?
string
.:
children
React.ReactNode
-
className?
string
''
style?
React.CSSProperties
{}