那个让我熬夜到两点的数字滚动效果,原来是这样实现的!
缘起:被某度首页种草的计数器
昨晚刷着手机,突然看到某度首页那个酷炫的AI调用次数计数器——数字像滚筒洗衣机一样唰唰滚动,最后稳稳停在目标数值。作为一个前端小菜鸟,我眼睛"叮"地亮了起来:"这个效果!我一定要自己实现出来!"
踩坑之路:从"简单"到"真香"
第一版尝试:CSS关键帧硬刚
/* 天真的初始方案 */
@keyframes roll {
0% { transform: translateY(0); }
100% { transform: translateY(-600px); }
}
结果数字像跳楼一样直上直下,完全没有丝滑的滚动感。这时候我才意识到,某度那个效果的精髓在于模拟真实物理运动的惯性。
凌晨一点的顿悟时刻
当我打开开发者工具仔细分析某度实现时,发现了三个关键点:
- 每个数字位的滚动速度不同,像水流一样依次启动
- 数字到达目标后还会轻微回弹,就像刹车时的惯性
- 滚动轨迹不是匀速,而是先快后慢的缓动效果
核心实现:物理惯性模拟的奥秘
灵魂参数表(调了整整一下午!)
const CONFIG = {
DURATION: 2000, // 总动画时长(手抖调成200会鬼畜)
ROLL_COUNT: 2, // 额外空转圈数(像洗衣机甩干)
DELAY_BETWEEN_DIGITS: 40, // 数字间延迟(制造波浪感)
DIGIT_HEIGHT: 60, // 单个数字高度(改这个要老命)
};
让我掉了一把头发的核心算法
// 这个公式我对着墙壁比划了半小时才理解
const targetY = -(CONFIG.ROLL_COUNT * 10 + targetDigit) * CONFIG.DIGIT_HEIGHT;
// 举个例子:要显示数字7,实际要滚动(2圈*10 +7)=27个数字的高度
// 这样就有"先转两圈再慢慢停到7"的效果!
最终折腾出来的DOM结构
<!-- 每个数字位都是独立滚筒 -->
<div class="digit-container">
<div class="digit-list" style="transition: transform 1.8s ease-out">
<div class="digit">0</div>
<div class="digit">1</div>
...(此处省略20个数字)
<div class="digit">9</div>
</div>
</div>
🌟完整源码(含详细注释版)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数字滚动计数器效果</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
}
.counter-container {
display: flex;
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.digit-container {
width: 40px;
height: 60px;
overflow: hidden;
position: relative;
margin: 0 2px;
}
.digit-list {
position: absolute;
transition: transform 2s ease-in-out;
transform: translateY(0);
}
.digit {
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
height: 60px;
font-weight: bold;
}
.prefix,
.suffix {
font-size: 24px;
margin: 0 10px;
align-self: center;
}
#counter {
display: flex;
}
</style>
</head>
<body>
<div class="counter-container">
<div class="prefix">今日已解决</div>
<div id="counter"></div>
<div class="suffix">个问题</div>
</div>
<script>
// 配置参数
const CONFIG = {
DURATION: 2000, // 动画持续时间(毫秒)
ROLL_COUNT: 2, // 数字滚动的额外循环次数
DELAY_BETWEEN_DIGITS: 40, // 数字之间的延迟时间(毫秒)
DIGIT_HEIGHT: 60, // 数字高度(像素)
TARGET_NUMBER: 7140909, // 目标数字
};
function createCounter(targetNumber) {
const counterEl = document.getElementById("counter");
const digits = targetNumber.toString().split("");
// 为每个数字创建容器和滚动效果
digits.forEach((digit, index) => {
const digitContainer = document.createElement("div");
digitContainer.className = "digit-container";
const digitList = document.createElement("div");
digitList.className = "digit-list";
// 设置动画延迟,从左到右依次延迟
const delay =
(digits.length - index - 1) * CONFIG.DELAY_BETWEEN_DIGITS;
digitList.style.transition = `transform ${
CONFIG.DURATION - delay
}ms ease-in-out`;
// 创建滚动序列(0-9 重复多次)
for (let i = 0; i <= CONFIG.ROLL_COUNT; i++) {
for (let j = 0; j < 10; j++) {
const digitEl = document.createElement("div");
digitEl.className = "digit";
digitEl.textContent = j;
digitList.appendChild(digitEl);
}
}
digitContainer.appendChild(digitList);
counterEl.appendChild(digitContainer);
});
// 开始动画
setTimeout(() => {
animateToTarget(targetNumber);
}, 100);
}
// 启动动画
function animateToTarget(targetNumber) {
const digits = targetNumber.toString().split("");
const digitLists = document.querySelectorAll(".digit-list");
digitLists.forEach((list, i) => {
const targetDigit = parseInt(digits[i], 10);
//关键计算公式!(总滚动距离 = 惯性圈数*10 + 目标数字)
const extraRolls = CONFIG.ROLL_COUNT * 10;
const targetY = -(extraRolls + targetDigit) * CONFIG.DIGIT_HEIGHT;
list.style.transform = `translateY(${targetY}px)`;
});
}
// 初始化计数器
window.onload = function () {
createCounter(CONFIG.TARGET_NUMBER);
};
</script>
</body>
</html>
效果:🎉🎉
后记:向某度工程师致敬
当我终于复现出这个效果时,已经是凌晨两点半。看着屏幕上流畅滚动的数字,突然想起《月亮与六便士》里的一句话:"美是难的"。这个看似简单的效果,背后是对用户体验的极致追求——多一圈则浮夸,少一圈则生硬的微妙平衡。
或许这就是前端的魅力所在:用逻辑雕琢美感,让理性的数字跳起感性的芭蕾。如果这个实现过程对你有启发,欢迎点赞+关注+转发,也欢迎在评论区留下你的优化方案~