组件
文本扰码 Text Scramble
随机字符逐步解析为目标文本的扰码效果。
基础示例
Loading…
pnpm dlx wui@latest add @wui/text-scramble"use client"
import * as React from "react"
import { useReducedMotion } from "motion/react"
import { cn } from "@/lib/utils"
const defaultCharacterSet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&*"
export interface TextScrambleProps extends React.ComponentProps<"p"> {
/** Final text revealed by the scramble. */
children: string
/** HTML element rendered by the component. @default "p" */
as?: React.ElementType
/** Total reveal duration in seconds. @default 0.8 */
duration?: number
/** Seconds between scramble frames. @default 0.04 */
speed?: number
/** Characters sampled while scrambling. */
characterSet?: string
/** Start the effect. Toggling false then true replays it. @default true */
trigger?: boolean
/** Called after every character reaches its final value. */
onScrambleComplete?: () => void
}
/** Resolves randomized characters into the supplied text. */
function TextScramble({
children,
as = "p",
duration = 0.8,
speed = 0.04,
characterSet = defaultCharacterSet,
trigger = true,
onScrambleComplete,
className,
...props
}: TextScrambleProps) {
const Component = as
const reduceMotion = useReducedMotion()
const [display, setDisplay] = React.useState(children)
React.useEffect(() => {
if (!trigger || reduceMotion) {
setDisplay(children)
return
}
const startedAt = performance.now()
const frameDuration = Math.max(speed * 1000, 16)
const totalDuration = Math.max(duration * 1000, frameDuration)
const timer = window.setInterval(() => {
const progress = Math.min(
(performance.now() - startedAt) / totalDuration,
1
)
const resolved = Math.floor(progress * children.length)
setDisplay(
Array.from(children)
.map((character, index) => {
if (/\s/.test(character) || index < resolved) return character
return (
characterSet[Math.floor(Math.random() * characterSet.length)] ??
character
)
})
.join("")
)
if (progress >= 1) {
window.clearInterval(timer)
setDisplay(children)
onScrambleComplete?.()
}
}, frameDuration)
return () => window.clearInterval(timer)
}, [
characterSet,
children,
duration,
onScrambleComplete,
reduceMotion,
speed,
trigger,
])
return (
<Component
aria-label={children}
data-slot="text-scramble"
className={cn("font-mono", className)}
{...props}
>
<span aria-hidden="true">{display}</span>
</Component>
)
}
export { TextScramble }
组件作用
TextScramble 适合生成、解码或终端语义的短状态。随机字符只作为视觉层,读屏始终获得最终文本。避免对正文和重要错误信息使用。
组件属性
| Prop | Type | Default | Description |
|---|---|---|---|
| children * | string | — | Final text revealed by the scramble. |
| as | ElementType<any, keyof IntrinsicElements> | p | HTML element rendered by the component. |
| duration | number | 0.8 | Total reveal duration in seconds. |
| speed | number | 0.04 | Seconds between scramble frames. |
| characterSet | string | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&* | Characters sampled while scrambling. |
| trigger | boolean | true | Start the effect. Toggling false then true replays it. |
| onScrambleComplete | (() => void) | — | Called after every character reaches its final value. |
事件
onScrambleComplete 在最终文本完全解析后触发。将 trigger 从 false 切换为 true 可以重播。
扩展使用
通过 characterSet 限制随机字符风格,例如仅用 01 营造二进制效果。duration 控制总时长,speed 控制随机字符刷新间隔。