前言
最近在做一个AI配置的组件,突然有了这个做动画的简单初始想法
例如: 当我选择 GPT-5.2 mini 去替换 GPT-5.1 时能不能前缀 GPT-5. 不动后,面的 2 mini 替换 1 执行一个动画
捣鼓了一段时间,于是Smart Ticker 诞生了
官网有很多场景演示 Demo :Smart Ticker Live Preview
GitHub仓库,详情见README :tombcato/smart-ticker
Gif有点掉帧,详情见官网

欢迎去 GitHub 点个 Star 🌟,或者在评论区提 Issue!
为什么做 Smart Ticker?
市面上其实已经有不少数字滚动库了,为什么还要造这个轮子?最大的痛点是:它们都不支持中文、Emoji 或者混排替换。
| 特性 / 库 | Smart Ticker 🚀 | Odometer.js | React CountUp | Number Flow |
|---|---|---|---|---|
| 核心机制 | Levenshtein Diff (智能最短路径) | 数值轮播 (0-9 循环) | 数值插值 (数字跳动) | 布局过渡 (Layout transition) |
| 字符支持 | 全字符集 (中英文/Emoji/符号/数字) | 仅数字 | 仅数字 | 主要是数字 |
| 动画效果 | 精准点对点 (只动变化的字符) | 滚轮式 (必须滚过中间数字) | 只有数字在变,无位移 | 现代且平滑 |
| 中文/全角适配 | 自动宽窄字符适配 | 容易挤压或乱码 | N/A | 需手动处理 |
| 多框架支持 | React + Vue 3 (同构核心) | JS Wrapper | React | 多框架 |
| 动画中断 | 平滑流体过渡 | 重置或生硬跳转 | 平滑 | 平滑 |
最大的区别在于:大多数库是把内容当成"数字"处理,而 Smart Ticker 是把内容当成 "字符串" 处理。这意味着你可以用它来滚动的不仅仅是金额,还可以是用户名、状态文本等等。
核心难题:如何让动画"聪明"起来?
如果你直接把 1234 变成 1254,最笨的办法是把 3 销毁,创建 5。但我们想要的是:只有 3 变成 5,其他数字保持静止。
如果字符串长度不一样呢?比如 Smart 变成 Start。
- 笨办法:5 个字母全换。
- 聪明办法 :
S,a,r,t不动,把中间的m删了,插入一个t?不对,是m变成了t。
这本质上是一个 Diff 问题 。我们需要找到从"旧字符串"到"新字符串"的最短编辑路径。
算法引入:Levenshtein Distance
这里我引入了 Levenshtein Distance(编辑距离) 算法的变种。通常我们用它来计算两个字符串相似度,但在这里,我用它来生成操作队列。
通过动态规划矩阵,我们可以计算出每一个字符应该执行什么动作:
- SAME (不变):位置不变,UI 保持静止。
- MODIFY (修改) :字符变了(如
3->5),执行滚动动画。 - INSERT (插入):新串变长,执行"挤入"动画。
- DELETE (删除):新串变短,执行"挤出"动画。
代码实现
在 Core 模块中,我构建了一个 (N+1) x (M+1) 的矩阵,回溯计算最优路径:
typescript
// 简化的回溯逻辑
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && oldStr[i-1] === newStr[j-1]) {
// 字符相等 -> SAME
actions.unshift({ type: 'SAME', char: oldStr[i-1] });
i--; j--;
} else if (costMatrix[i][j] === costMatrix[i-1][j-1] + 1) {
// 替换操作 -> MODIFY (Scroll)
actions.unshift({ type: 'MODIFY', from: oldStr[i-1], to: newStr[j-1] });
i--; j--;
} else if (/* ... */) {
// 处理 INSERT / DELETE
}
}
正是因为有了这个算法,Smart Ticker 才能做到:
- 100 -> 105 :只有
5在动。 - Downloading... -> Done:智能识别哪些字母保留,哪些字母滚走。
挑战二:不仅是数字,中英字母Emoji我全都要!
搞定了算法,渲染层又是一个坑。 中文字符(全角)和英文字符(半角)宽度不一样。如果强行用等宽字体,中文会被挤成一团或者英文间距过大。
我也遇到过这种"灾难现场",中文挤在一起根本没法看。
解决方案: 我实现了一个 charWidth 动态计算机制。
- 检测字符类型:通过 Unicode 范围检测当前字符是半角还是全角(或者是 Emoji)。
- 动态基准宽度 :
- 半角字符(数字/英文):基准宽
0.8em(紧凑美观)。 - 全角字符(中文):基准宽
1.25em(防止重叠)。 - Emoji:特殊处理,确保不被截断。
- 半角字符(数字/英文):基准宽
这样一来,无论输 BTC $98,000 还是 哈基米 🐱,排版都稳如老狗。
挑战三:双框架支持 (React + Vue)
有些库只有 React 版,我的AI配置组件项目是基于 Vue。为了雨露均沾,我采用 "Core + Adapter" 的架构设计。
- src/core/: 纯 TypeScript 实现 Diff 算法、字符列表生成、缓动函数。不依赖任何框架。
- src/components/Ticker.tsx : React 封装层,使用
useRef和RAF管理动画。 - src/components/vue/Ticker.vue : Vue 3 封装层,使用
watch和requestAnimationFrame。
结果:两套组件 API 完全一致,维护起来也极其轻松。
最终效果 & 如何使用
现在,你只需要一行代码就能用上这个"聪明"的组件:
安装:
bash
npm install @tombcato/smart-ticker
React 使用:
tsx
import { Ticker } from '@tombcato/smart-ticker';
import '@tombcato/smart-ticker/style.css';
<Ticker value="98,456.32" />
Vue 使用:
vue
<script setup>
import { Ticker } from '@tombcato/smart-ticker/vue';
import '@tombcato/smart-ticker/style.css';
</script>
<template>
<Ticker value="🚀 发射成功" />
</template>
总结
这就是一个从小灵感到落地实现的过程,坚持走下去从实现小的想法到实现大的想法,如果阿祖你能看到这里请帮我点个star,感谢感谢!