TextMorph 文本形变
把字符串切成有身份的段再 diff,只让真正变化的部分动起来;数字按位值滚动,容器宽高与字符同曲线过渡,支持弹簧缓动与打断续跑。
TextMorph 文本形变
与
TxTextTransformer的整串淡化不同:这里的动效单位是字符段,不是整个字符串。存活的段做 FLIP 位移,新增的段从最近的锚点淡入,离场的段脱离文档流后淡出,数字额外按位值纵向滚动。
基础用法
TextMorph
示例加载中...
数字位值滚动
数字默认按位值匹配,而不是从左到右:1,204 → 1,318 只滚百位和十位,千位原地不动;千分位逗号跟着量级走,999,999 → 1,000,000 时会滑过一整组。
量级跳跃达到 3 位及以上时不再携带任何数字——那时候数字已经糊成一片,整体替换才是对的。
把 numbers 关掉可退回字符级形变。
Number place value
示例加载中...
弹簧缓动
spring 接预设名或物理系数,来自 TuffEx 内部与 TxLiquid、TxSlider 同一套弹簧编译器,所以整库的动效语言是一致的。弹簧同时决定曲线与时长,因此设了它之后 durationMs 与 easing 都会被忽略。
Spring presets
示例加载中...
API
TxTextMorph Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
text | string | number | - | 要渲染的值。数字会先按 locale + decimals 格式化。 |
tag | string | span | 根节点 HTML 标签。 |
durationMs | number | 400 | 形变时长(ms)。设了 spring 时被忽略。 |
easing | string | cubic-bezier(0.19, 1, 0.22, 1) | CSS 缓动函数。设了 spring 时被忽略。 |
spring | 'snappy' | 'smooth' | 'bouncy' | { stiffness?, damping?, mass? } | - | 弹簧物理。同时提供曲线与时长。 |
scale | boolean | true | 离场段是否带缩放。 |
numbers | boolean | true | 数字按位值滚动;关闭后退回字符级形变。 |
decimals | number | - | 小数位数。仅当 text 是数字时生效。 |
locale | string | en | 用于分段与数字格式化。 |
cursorIndex | number | - | 光标位置。输入框场景下把单个数字从位值匹配切成光标匹配。 |
disabled | boolean | false | 跳过动画,直接写入值。 |
respectReducedMotion | boolean | true | 把 prefers-reduced-motion: reduce 当作 disabled。 |
debug | boolean | false | 给根节点和每个段描边,用于排查形变异常。 |
TxTextMorph Events
| 事件名 | 参数 | 说明 |
|---|---|---|
animation-start | - | 一次形变开始时触发;首帧渲染不触发。 |
animation-complete | - | 形变自然走完。 |
animation-cancel | - | 形变被下一次更新打断。 |
每次形变里
animation-complete与animation-cancel恰好触发其一。
交互契约
- 完整值以一个视觉隐藏但可读的
[tx-morph-sr]节点存在;所有段元素都是aria-hidden,屏幕阅读器只会读到完整值一次。 - 段元素由引擎命令式创建,因此组件样式不是 scoped 的,作用域由
tx-morph-*属性名保证。 - 挂载后 Vue 不再渲染子节点——引擎接管了它们。首帧的纯文本只为 SSR 水合对齐。
prefers-reduced-motion: reduce或disabled下直接写入textContent,并清空内部段记录,避免恢复动效后拿已移除的元素做 FLIP。- 形变中途再次更新会读取当前动画的速度并携带进新曲线,所以高频更新不会把每条曲线都停在起步阶段。
- 根节点是
white-space: nowrap,换行由值里的\n生成<br>;它不做软换行,也不做省略号截断。 cursorIndex只在整个值里恰好有一个数字时生效。
最佳实践
- 计数器、金额、进度百分比这类"同一个量在变"的值优先用它;
numbers的位值匹配就是为这类值写的。 - 输入框里跟随用户键入的数字要传
cursorIndex,否则在20前面插一位会被理解成整列重新编号。 - 需要软换行或省略号截断的文本用
TxTextTransformer的mode="fade",引擎表达不了这两件事。 - 同一屏里不要放太多高频实例:每个字符都是一个带
will-change的元素。 - 想让容器跟着变化就直接用它——引擎自己动画宽高,不需要再套
TxAutoSizer。
Source
- Component source:
packages/tuffex/packages/components/src/text-morph/src/TxTextMorph.vue。 - Engine:
packages/tuffex/packages/components/src/text-morph/src/engine/,移植自 lochie/torph(MIT),弹簧层换成了 TuffEx 自己的liquid/src/spring.ts。 - Types:
packages/tuffex/packages/components/src/text-morph/src/types.ts导出TextMorphProps。 - Export alias:
packages/tuffex/packages/components/src/text-morph/index.ts导出TextMorph、TxTextMorph、TextMorphProps、TxTextMorphInstance,以及引擎的TextMorphEngine/MorphController。 - Coverage:
packages/tuffex/packages/components/src/text-morph/__tests__/下的engine.test.ts与text-morph.test.ts覆盖分段、diff 配对、位值匹配、弹簧融合与 reduced-motion 降级。
查看源码
packages/tuffex/packages/components/src/text-morph/index.ts