Scramble Hover
Scramble Hover é um componente de texto para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
SmoothUI
MIT
text
O que é
Scramble Hover é um componente de texto para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Não precisa de pacotes além do que o projeto já tem
Licença
Este componente vem de SmoothUI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Scramble Hover
"use client";
import type React from "react";
import { useEffect, useRef, useState } from "react";
export interface ScrambleHoverProps {
children: string;
className?: string;
duration?: number; // total animation duration in ms
speed?: number; // interval between scrambles in ms
}
const CHARACTERS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=<>?".split(
""
);
function scrambleText(original: string) {
return original
.split("")
.map((char) =>
char === " "
? " "
: CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)]
)
.join("");
}
const ScrambleHover: React.FC<ScrambleHoverProps> = ({
children,
duration = 600,
speed = 30,
className = "",
}) => {
const [display, setDisplay] = useState(children);
const [shouldReduceMotion, setShouldReduceMotion] = useState(false);
const [isHoverDevice, setIsHoverDevice] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const hoverQuery = window.matchMedia("(hover: hover) and (pointer: fine)");
setShouldReduceMotion(motionQuery.matches);
setIsHoverDevice(hoverQuery.matches);
const handleMotionChange = (e: MediaQueryListEvent) => {
setShouldReduceMotion(e.matches);
};
const handleHoverChange = (e: MediaQueryListEvent) => {
setIsHoverDevice(e.matches);
};
motionQuery.addEventListener("change", handleMotionChange);
hoverQuery.addEventListener("change", handleHoverChange);
return () => {
motionQuery.removeEventListener("change", handleMotionChange);
hoverQuery.removeEventListener("change", handleHoverChange);
};
}, []);
const handleMouseEnter = () => {
if (shouldReduceMotion || !isHoverDevice) {
return;
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
intervalRef.current = setInterval(() => {
setDisplay(() => scrambleText(children));
}, speed);
timeoutRef.current = setTimeout(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
setDisplay(children);
}, duration);
};
const handleMouseLeave = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setDisplay(children);
};
return (
<button
className={className}
onBlur={handleMouseLeave}
onFocus={isHoverDevice ? handleMouseEnter : undefined}
onMouseEnter={isHoverDevice ? handleMouseEnter : undefined}
onMouseLeave={handleMouseLeave}
style={{
background: "none",
border: "none",
color: "inherit",
cursor: "pointer",
display: "inline-block",
font: "inherit",
padding: 0,
textAlign: "inherit",
}}
type="button"
>
{display}
</button>
);
};
export default ScrambleHover;