必须用tabular-nums+translateY()+cubic-bezier(0.33,1,0.68,1)三者缺一不可:等宽字体确保数字对齐,translateY()实现精准位移,该贝塞尔曲线专为离散数字切换优化缓动,避免顿挫与错位。

必须用 tabular-nums + translateY() + cubic-bezier(0.33, 1, 0.68, 1),三者缺一不可;否则滚动必错位或顿挫。
为什么数字一动就歪?等宽字体不是优化项,是硬性前提
“1”比“0”窄、“7”比“8”细——这是绝大多数字体的默认行为。堆叠 0–9 后用 transform: translateY() 位移,上下数字边缘对不齐,视觉上就是“抖动”或“错行”。font-variant-numeric: tabular-nums 强制所有数字宽度一致,但仅在支持该特性的字体中生效(如 SF Mono、IBM Plex Mono)。纯 monospace 不保证数字等宽,iOS Safari 15.4 之前也不认 tabular-nums。
实操建议:
- 显式声明字体链:
font-family: 'SF Mono', 'Consolas', 'Roboto Mono', monospace - 务必加
font-variant-numeric: tabular-nums,并在 DevTools 的 Computed 面板验证是否生效 - 用 ruler 工具量多个并排数字(如
0123456789)的像素宽度,确认完全相等
translateY(calc(var(--digit) * -100%)) 公式里一个符号都不能错
这个公式依赖两个前提:容器高度 = 单个数字高度,且数字垂直堆叠占满整列。位移目标是让第 n 个数字精准居中,所以必须是负号 × 100%,正号会直接滚出视野,用 px 会破坏响应式。
立即学习“前端免费学习笔记(深入)”;
常见翻车点:
-
calc(var(--digit) * 100%)→ 向下偏移,看不见目标数字 -
calc(-100% * var(--digit))→ 语法合法,但旧版 Safari 可能解析失败 - 容器
height设为48px,但line-height是46px→ 单行实际高度浮动,100% 基准失准 - 用
em或rem设容器高 → 父级font-size变动时,整个滚动节奏崩塌
为什么 cubic-bezier(0.33, 1, 0.68, 1) 不能换?
数字切换不是连续插值,而是离散跳变:从显示“3”瞬间切到“4”。默认 ease 在起始和结束处加速度突变,人眼明显感知“卡一下”。这个贝塞尔曲线是专为数字滚动调优的:起始缓入、中间接近线性、结束强缓出,模拟真实滚轮惯性停稳的过程。
别试这些替代方案:
-
ease-in-out→ 起始太慢,结束拖尾,像生锈齿轮 -
linear→ 缺乏停顿感,数字“滑过”而非“落定” - 省略
transition直接靠 JS 改style.transform→ 失去 CSS 合成层加速,掉帧明显
JS 更新 --digit 时最容易漏的关键操作
CSS 自定义属性必须用 el.style.setProperty('--digit', n) 更新。用 el.setAttribute('style', ...) 会覆盖整个内联样式,导致之前写的 transition、font-variant-numeric 全部丢失。
其他细节:
- 容器必须设
overflow: hidden,否则滚动时看到上下溢出的数字 - 数字列表 HTML 结构建议写成
<div class="digit-list">0123456789</div>,不要用<span>包每个数字——增加 DOM 节点数,无必要 - IE 完全不支持
calc()中的自定义属性,需降级为 JS 直接计算像素值:el.style.transform = `translateY(-${n * h}px)`
真正难的不是写出第一版滚动效果,而是让 6 位数字在 iOS 14、Chrome 90、Safari 15.3 下全部对齐、不闪、不卡——那几个被忽略的字体兼容性边界和贝塞尔参数,才是上线前最后一道坎。


















