组件
文本翻滚 Text Roll
悬停或挂载时逐字符翻滚的文字效果。
基础示例
Loading…
pnpm dlx wui@latest add @wui/text-roll"use client"
import * as React from "react"
import {
motion,
useReducedMotion,
type Transition,
type Variants,
} from "motion/react"
import { cn } from "@/lib/utils"
const defaultVariants: Variants = {
rest: { y: "0%" },
hover: { y: "-50%" },
}
export interface TextRollProps extends React.ComponentProps<"span"> {
/** Text rolled character by character. */
children: string
/** Duration of each character roll in seconds. @default 0.45 */
duration?: number
/** Delay for each character entering the roll. */
getEnterDelay?: (index: number) => number
/** Delay for each character returning to rest. */
getExitDelay?: (index: number) => number
/** Motion transition merged into every character. */
transition?: Transition
/** Rest and hover states for each character track. */
variants?: Variants
/** Play on hover or immediately on mount. @default "hover" */
trigger?: "hover" | "mount"
}
/** Rolls a second copy of each character into view. */
function TextRoll({
children,
className,
duration = 0.45,
getEnterDelay = (index) => index * 0.025,
getExitDelay = (index) => index * 0.02,
transition,
variants = defaultVariants,
trigger = "hover",
onMouseEnter,
onMouseLeave,
...props
}: TextRollProps) {
const reduceMotion = useReducedMotion()
const [hovered, setHovered] = React.useState(false)
const active = trigger === "mount" || hovered
return (
<span
aria-label={children}
data-slot="text-roll"
className={cn("inline-flex", className)}
onMouseEnter={(event) => {
setHovered(true)
onMouseEnter?.(event)
}}
onMouseLeave={(event) => {
setHovered(false)
onMouseLeave?.(event)
}}
{...props}
>
{Array.from(children).map((character, index) => (
<span
aria-hidden="true"
data-slot="text-roll-character"
className="inline-block h-[1em] overflow-hidden leading-none"
key={`${character}-${index}`}
>
<motion.span
className="flex flex-col"
variants={variants}
initial="rest"
animate={reduceMotion ? "rest" : active ? "hover" : "rest"}
transition={{
duration,
ease: [0.22, 1, 0.36, 1],
delay: active ? getEnterDelay(index) : getExitDelay(index),
...transition,
}}
>
<span className="block h-[1em] whitespace-pre">
{character === " " ? "\u00a0" : character}
</span>
<span className="block h-[1em] whitespace-pre">
{character === " " ? "\u00a0" : character}
</span>
</motion.span>
</span>
))}
</span>
)
}
export { TextRoll }
组件作用
TextRoll 复制每个字符并纵向滚动第二份内容,适合导航链接、简短按钮和展示标题。默认悬停触发,不会持续争夺注意力。
组件属性
| Prop | Type | Default | Description |
|---|---|---|---|
| children * | string | — | Text rolled character by character. |
| duration | number | 0.45 | Duration of each character roll in seconds. |
| getEnterDelay | ((index: number) => number) | (index) => index * 0.025 | Delay for each character entering the roll. |
| getExitDelay | ((index: number) => number) | (index) => index * 0.02 | Delay for each character returning to rest. |
| transition | Transition | — | Motion transition merged into every character. |
| variants | Variants | { rest: { y: "0%" }, hover: { y: "-50%" }, } | Rest and hover states for each character track. |
| trigger | "hover" | "mount" | hover | Play on hover or immediately on mount. |
事件
组件使用鼠标进入和离开控制默认效果,其余 <span> 事件继续透传。减少动态效果时保留静态文字。
扩展使用
使用 trigger="mount" 可在挂载时播放,getEnterDelay 和 getExitDelay 用来改变字符波次。自定义 variants 时保留 rest 与 hover 两个状态名。