如何让数字计数器在滚动到可视区域时才开始动画

7次阅读

如何让数字计数器在滚动到可视区域时才开始动画

本文介绍如何使用原生 javascript 实现“懒启动”数字计数器——仅当用户滚动至目标区块进入视口时,才触发动画,避免页面加载即执行导致的无效计数。

在网页开发中,常见的数字计数器(如统计访问量、会员数、满意度等)若在页面加载时立即执行,而目标区块位于页面底部(如页脚附近),会导致用户尚未看到该区域时动画已结束,体验割裂且不直观。解决核心在于:将动画触发时机从 onload 改为“元素进入视口”的检测时机

✅ 推荐方案:基于 getBoundingClientRect() 的视口检测

我们不再依赖 window.onload,而是监听 scroll 事件,并动态判断 .container 区块是否已进入用户可视区域(viewport)。关键原理是:

  • element.getBoundingClientRect().top 返回元素上边界距离视口顶部的像素值(滚动时该值会从正变负);
  • window.scrollY 表示当前垂直滚动距离(文档顶部到视口顶部的距离);
  • 当 scrollY + window.innerHeight >= element.offsetTop 或更精准地用 getBoundingClientRect().top

但为简洁可靠,推荐使用以下优化实现(无需 jquery,纯原生):

// 获取目标元素 const container = document.querySelector('.container'); const text1 = document.getElementById('0101'); const text2 = document.getElementById('0102'); const text3 = document.getElementById('0103');  // 动画函数(保持原逻辑,已优化兼容性) function animate(obj, initVal, lastVal, duration) {   let startTime = null;   const step = (timestamp) => {     if (!startTime) startTime = timestamp;     const progress = Math.min((timestamp - startTime) / duration, 1);     obj.textContent = Math.floor(progress * (lastVal - initVal) + initVal);     if (progress < 1) requestAnimationFrame(step);   };   requestAnimationFrame(step); }  // 防重复触发标志 let animationStarted = false;  // 滚动监听函数 function checkAndAnimate() {   const rect = container.getBoundingClientRect();   // 当容器上边缘进入视口(即 rect.top <= 视口高度)且未触发过时启动   if (rect.top <= window.innerHeight && !animationStarted) {     animate(text1, 0, 100, 3000);     animate(text2, 0, 300, 3000);     animate(text3, 0, 100, 3000);     animationStarted = true;   } }  // 绑定滚动事件(建议节流以提升性能) let ticking = false; window.addEventListener('scroll', () => {   if (!ticking) {     requestAnimationFrame(() => {       checkAndAnimate();       ticking = false;     });     ticking = true;   } });  // 页面加载后立即检查一次(防止初始就在视口内) checkAndAnimate();

⚠️ 注意事项与最佳实践

  • ID 命名规范html 中 id=’0101′ 等纯数字 ID 不符合标准(html5 允许但易引发兼容性问题或 css 选择器歧义),建议改为语义化命名,如 id=”counter-visits”;
  • 性能优化:直接监听 scroll 易造成高频调用,务必使用 requestAnimationFrame 节流(如上所示),避免卡顿;
  • 兼容性增强:getBoundingClientRect() 在所有现代浏览器中均支持;若需支持旧版 IE,可改用 element.offsetTop 配合 scrollY 计算,但精度略低;
  • 更优替代方案:生产环境推荐使用 Intersection Observer API,它专为这类场景设计,性能更佳、代码更简洁:
const observer = new IntersectionObserver((entries) => {   entries.forEach(entry => {     if (entry.isIntersecting && !animationStarted) {       animate(text1, 0, 100, 3000);       animate(text2, 0, 300, 3000);       animate(text3, 0, 100, 3000);       animationStarted = true;       observer.unobserve(container); // 动画后停止监听     }   }); }, { threshold: 0.1 }); // 当 10% 区域可见时触发  observer.observe(container);

✅ 总结

让计数器“按需启动”,本质是将动画逻辑与用户行为(滚动)解耦,并通过视口检测建立精准触发条件。相比简单监听 onscroll,采用 IntersectionObserver 或带节流的 getBoundingClientRect 方案,既能保证用户体验流畅,又具备良好的可维护性与扩展性。记住:好的动画不是“一上来就动”,而是“你看见它时,它才开始呼吸”。

text=ZqhQzanResources