Smart Ticker - 支持任意字符的高性能文本差异动画滚动组件

前言

最近在做一个AI配置的组件,突然有了这个做动画的简单初始想法

例如: 当我选择 GPT-5.2 mini 去替换 GPT-5.1 时能不能前缀 GPT-5. 不动后,面的 2 mini 替换 1 执行一个动画

捣鼓了一段时间,于是Smart Ticker 诞生了

官网有很多场景演示 DemoSmart Ticker Live Preview
GitHub仓库,详情见READMEtombcato/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(编辑距离) 算法的变种。通常我们用它来计算两个字符串相似度,但在这里,我用它来生成操作队列

通过动态规划矩阵,我们可以计算出每一个字符应该执行什么动作:

  1. SAME (不变):位置不变,UI 保持静止。
  2. MODIFY (修改) :字符变了(如 3 -> 5),执行滚动动画。
  3. INSERT (插入):新串变长,执行"挤入"动画。
  4. 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 动态计算机制。

  1. 检测字符类型:通过 Unicode 范围检测当前字符是半角还是全角(或者是 Emoji)。
  2. 动态基准宽度
    • 半角字符(数字/英文):基准宽 0.8em(紧凑美观)。
    • 全角字符(中文):基准宽 1.25em(防止重叠)。
    • Emoji:特殊处理,确保不被截断。

这样一来,无论输 BTC $98,000 还是 哈基米 🐱,排版都稳如老狗。

挑战三:双框架支持 (React + Vue)

有些库只有 React 版,我的AI配置组件项目是基于 Vue。为了雨露均沾,我采用 "Core + Adapter" 的架构设计。

  • src/core/: 纯 TypeScript 实现 Diff 算法、字符列表生成、缓动函数。不依赖任何框架。
  • src/components/Ticker.tsx : React 封装层,使用 useRefRAF 管理动画。
  • src/components/vue/Ticker.vue : Vue 3 封装层,使用 watchrequestAnimationFrame

结果:两套组件 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,感谢感谢!

相关推荐
脱氧核糖核酸2 小时前
2026了你还只会写点prompt?从AI提示词到可控自动化的演进之路
前端
HabaraAi2 小时前
记一次发现 DataTransfer 的 getData 的有趣问题
前端
a17798877122 小时前
print.js打印
前端·javascript·html
小林攻城狮2 小时前
前端实时语音转写:原生 MediaRecorder API 实践
前端·vue.js
Sport2 小时前
用全会,问全废:CSS高频面试题
前端·javascript·面试
Maxkim2 小时前
「✍️JS原子笔记 」零基础吃透 Proxy 数据响应式
前端·javascript·面试
hashiqimiya2 小时前
vue前端打包配置后端代理
前端
小白咚2 小时前
npm在文件下输入运行命令,授权限制问题window
前端·npm·node.js
清平乐的技术专栏2 小时前
电脑自带Edge浏览器进行PDF文件合并
前端·edge·pdf