← каталог
Text Scramble
Текст «расшифровывается» из случайных символов на входе. GSAP-твин, клик — повтор.
gsaploadclickrevealintermediate~22 КБreduced-motion ✓
Пропсы
Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.
| Проп | Тип | По умолчанию | Что это |
|---|---|---|---|
| text? | string | "MOTIVA" | |
| style? | CSSProperties | — | |
| className? | string | — |
Установка
npx shadcn@latest add https://motiva.pages.dev/r/text-scramble.jsonзависимости: gsap@^3.15.0 · @gsap/react@^2.1.2
Что нужно в проекте до установки
components.jsonв корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.- Алиас
@объявлен дважды:"paths": {"@/*": ["./src/*"]}в корневомtsconfig.json(в Vite он часто содержит толькоreferences— тогда CLI создаёт папку@) иresolve.aliasв конфиге сборщика. - Токены подключены один раз на приложение:
import "@/styles/motiva-tokens.css"— они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему. - Своя типографика не должна спорить: если у вас на обёртке
text-align: center, он протечёт внутрь блоков. - Проект без TypeScript? Всё равно нужен минимальный
tsconfig.jsonсpathsи"tsx": trueвcomponents.json: CLI грузит tsconfig безусловно, а с"tsx": falseидёт заjsconfig.jsonи падает так же. Приехавший рядом.tsxможно не трогать — вам нужен.vanilla.js.
Экспорт
Код
"use client";
import { useRef, type CSSProperties } from "react";
import { useGSAP } from "@gsap/react";
import { scramble } from "@/lib/scramble";
export interface TextScrambleProps {
text?: string;
style?: CSSProperties;
className?: string;
}
/** Текст с эффектом «расшифровки» на входе (GSAP). Уважает reduced-motion. */
export function TextScramble({ text = "MOTIVA", style, className }: TextScrambleProps) {
const ref = useRef<HTMLSpanElement>(null);
useGSAP(
() => {
if (ref.current) scramble(ref.current, text);
},
{ dependencies: [text] },
);
return (
<span ref={ref} style={style} className={className}>
{text}
</span>
);
}
import gsap from "gsap";
const CHARS = "!<>-_\\/[]{}=+*^?#________";
/**
* Скрамбл-переход текста элемента к `text` через GSAP-твин.
* Уважает `prefers-reduced-motion`.
*
* scramble(el, "MOTIVA");
*/
export function scramble(el: HTMLElement, text: string, duration = 1.4): void {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
el.textContent = text;
return;
}
const queue = Array.from({ length: text.length }, (_, i) => ({
to: text[i] ?? "",
start: Math.floor(Math.random() * text.length),
end: Math.floor(text.length + Math.random() * text.length),
}));
const state = { frame: 0 };
gsap.to(state, {
frame: text.length * 2,
duration,
ease: "none",
onUpdate() {
let out = "";
for (const q of queue) {
if (state.frame >= q.end) out += q.to;
else if (state.frame >= q.start) out += CHARS[Math.floor(Math.random() * CHARS.length)];
}
el.textContent = out;
},
onComplete() {
el.textContent = text;
},
});
}
import gsap from "gsap";
const CHARS = "!<>-_\\/[]{}=+*^?#________";
/**
* Text Scramble — vanilla JS/GSAP (без React). Ядро то же, что в React-версии.
*
* <h1 id="title">MOTIVA</h1>
* <script type="module">
* import { scramble } from './text-scramble.vanilla.js';
* scramble(document.querySelector('#title'), 'MOTIVA'); // на входе
* // или по ховеру/клику: el.addEventListener('mouseenter', () => scramble(el, el.dataset.text));
* </script>
*
* @param {HTMLElement} el
* @param {string} text — целевой текст
* @param {number} [duration] — сек
*/
export function scramble(el, text, duration = 1.4) {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
el.textContent = text;
return;
}
const queue = Array.from({ length: text.length }, (_, i) => ({
to: text[i] ?? "",
start: Math.floor(Math.random() * text.length),
end: Math.floor(text.length + Math.random() * text.length),
}));
const state = { frame: 0 };
gsap.to(state, {
frame: text.length * 2,
duration,
ease: "none",
onUpdate() {
let out = "";
for (const q of queue) {
if (state.frame >= q.end) out += q.to;
else if (state.frame >= q.start) out += CHARS[Math.floor(Math.random() * CHARS.length)];
}
el.textContent = out;
},
onComplete() {
el.textContent = text;
},
});
}
Похожие эффекты
лицензия: MITавтор: Motivaисточник: оригинал (Motiva)v1.0.0