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,感谢感谢!

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端