wui
组件

文本变形 Text Morph

在文字变化时让共享字符流畅地移动到新位置。

基础示例

Loading…
pnpm dlx wui@latest add @wui/text-morph
components/ui/text-morph.tsx
"use client"

import * as React from "react"
import { LayoutGroup, motion, useReducedMotion } from "motion/react"

import { cn } from "@/lib/utils"

export interface TextMorphProps extends React.ComponentProps<"p"> {
  /** Text whose shared characters morph between positions. */
  children: string
  /** HTML element rendered by the component. @default "p" */
  as?: React.ElementType
}

function characterKeys(text: string) {
  const seen = new Map<string, number>()
  return Array.from(text).map((character) => {
    const count = seen.get(character) ?? 0
    seen.set(character, count + 1)
    return `${character}-${count}`
  })
}

/** Morphs shared characters into their new positions when the text changes. */
function TextMorph({
  children,
  as = "p",
  className,
  ...props
}: TextMorphProps) {
  const reduceMotion = useReducedMotion()
  const Component = as
  const keys = React.useMemo(() => characterKeys(children), [children])

  if (reduceMotion) {
    return (
      <Component data-slot="text-morph" className={className} {...props}>
        {children}
      </Component>
    )
  }

  return (
    <LayoutGroup>
      <Component
        aria-label={children}
        data-slot="text-morph"
        className={cn("inline-flex", className)}
        {...props}
      >
        {Array.from(children).map((character, index) => (
          <motion.span
            aria-hidden="true"
            data-slot="text-morph-character"
            className="inline-block whitespace-pre"
            layout
            layoutId={keys[index]}
            transition={{ type: "spring", stiffness: 420, damping: 35 }}
            key={keys[index]}
          >
            {character === " " ? "\u00a0" : character}
          </motion.span>
        ))}
      </Component>
    </LayoutGroup>
  )
}

export { TextMorph }

组件作用

TextMorph 为新旧文本中相同的字符建立布局关联,适合按钮标签、短状态和简短标题。字符越相近,形变越自然;完全不同的长文本更适合 TextEffect

组件属性

PropTypeDefaultDescription
children *stringText whose shared characters morph between positions.
asElementType<any, keyof IntrinsicElements>pHTML element rendered by the component.

事件

组件没有自有回调,更新 children 即会触发布局动画,并透传底层元素的原生事件。

扩展使用

通过 as="span" 放入按钮或句子中。视觉字符对辅助技术隐藏,完整的新文本会作为元素标签读取;减少动态效果时直接替换文字。