wui
组件

文本扰码 Text Scramble

随机字符逐步解析为目标文本的扰码效果。

基础示例

Loading…
pnpm dlx wui@latest add @wui/text-scramble
components/ui/text-scramble.tsx
"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 适合生成、解码或终端语义的短状态。随机字符只作为视觉层,读屏始终获得最终文本。避免对正文和重要错误信息使用。

组件属性

PropTypeDefaultDescription
children *stringFinal text revealed by the scramble.
asElementType<any, keyof IntrinsicElements>pHTML element rendered by the component.
durationnumber0.8Total reveal duration in seconds.
speednumber0.04Seconds between scramble frames.
characterSetstringABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&*Characters sampled while scrambling.
triggerbooleantrueStart the effect. Toggling false then true replays it.
onScrambleComplete(() => void)Called after every character reaches its final value.

事件

onScrambleComplete 在最终文本完全解析后触发。将 triggerfalse 切换为 true 可以重播。

扩展使用

通过 characterSet 限制随机字符风格,例如仅用 01 营造二进制效果。duration 控制总时长,speed 控制随机字符刷新间隔。