滚动离开skills区块时环形进度条重置失效问题如何解决
环形进度条滚动触发重置问题修复
原有代码的核心问题
- 滚动事件高频触发时会重复创建
setInterval定时器,切换状态前没有清空已有定时器实例,多个定时器并行运行会导致动画逻辑冲突、数值跳变 - 区块进入判断逻辑缺失:仅校验滚动位置是否超过skills区块顶部,没有校验滚动位置是否超出区块底部边界,向下滚动离开skills区块到下方第二区块时,依然会命中「进入区块」的判断分支,无法触发重置逻辑
- 重置分支判断条件错误:仅当计数器数值等于目标最大值时才执行递减操作,若动画未播放完成用户就滚动离开区块,重置逻辑完全不会执行
- 环形描边重置没有和百分比数值变化同步计算,直接硬编码设置为空白状态,和数字动画节奏不匹配
修复方案
- 新增状态标记位,记录当前进度条是否处于激活播放状态,避免重复触发动画
- 完善区块可见性判断规则:只有当滚动位置处于「区块顶部偏移-触发阈值」到「区块底部位置」区间内时,才判定为进入skills区块,其余滚动位置均判定为离开区块
- 每次切换进入/离开状态前,先清空所有正在运行的定时器,避免定时器叠加导致的动画异常
- 统一百分比数值和环形描边偏移量的计算逻辑,递增、递减过程中同步更新两个状态,保证动画节奏完全一致
修复后可直接运行的JS代码
原有CSS和HTML结构无需调整,将原有JS逻辑替换为以下代码即可:
const numbers = document.querySelectorAll(".number"); const svgEl = document.querySelectorAll('svg circle'); const section = document.querySelector('#skills'); const counters = Array(numbers.length).fill(0); const intervals = Array(numbers.length); // 状态标记:记录进度条是否处于激活播放状态 let isActive = false; // 重置进度条到0%状态 function resetProgress() { // 先清空所有运行中的定时器 intervals.forEach(timer => clearInterval(timer)); numbers.forEach((number, index) => { intervals[index] = setInterval(() => { if (counters[index] <= 0) { clearInterval(intervals[index]); counters[index] = 0; number.innerHTML = "0%"; svgEl[index].style.strokeDashoffset = 472; } else { counters[index] -= 1; number.innerHTML = counters[index] + "%"; // 同步计算描边偏移量,和数字变化保持一致 svgEl[index].style.strokeDashoffset = Math.floor(472 - 440 * (counters[index] / 100)); } }, 20); }); isActive = false; } // 播放进度条从0到目标值的动画 function playProgress() { // 先清空所有运行中的定时器 intervals.forEach(timer => clearInterval(timer)); counters.fill(0); numbers.forEach((number, index) => { const target = parseInt(number.dataset.num); intervals[index] = setInterval(() => { if (counters[index] >= target) { clearInterval(intervals[index]); counters[index] = target; number.innerHTML = `${target}%`; svgEl[index].style.strokeDashoffset = Math.floor(472 - 440 * (target / 100)); } else { counters[index] += 1; number.innerHTML = `${counters[index]}%`; svgEl[index].style.strokeDashoffset = Math.floor(472 - 440 * (counters[index] / 100)); } }, 20); }); isActive = true; } window.onscroll = function () { const sectionTop = section.offsetTop - 50; const sectionBottom = section.offsetTop + section.offsetHeight; const currentScroll = window.scrollY; const isInSection = currentScroll >= sectionTop && currentScroll < sectionBottom; if (isInSection && !isActive) { playProgress(); } else if (!isInSection && isActive) { resetProgress(); } }
补充说明:需要确保每个
.skill-level容器内包含对应的SVG环形DOM结构,否则svg circle选择器无法获取到描边元素,会导致环形填充动画不生效。
内容的提问来源于stack exchange,提问作者Mohamad
相关产品推荐
相关产品推荐

