使用forEach()时numberIncrease函数仅执行一次的JavaScript问题
问题原因
- 核心问题出在
numberIncrease()函数内部:每次调用它时,都会通过document.querySelector('.counter')重新获取元素,而这个方法只会返回匹配到的第一个元素——所以不管你在forEach里遍历多少个counter,函数始终只对第一个元素执行动画。 - 另外,你在forEach循环中调用
numberIncrease()时,没有把当前遍历到的counter元素传递给函数,导致函数根本不知道要针对哪个元素处理。
修复方案
不需要更换循环类型,只需要调整函数的参数传递和内部元素获取逻辑:
- 让
numberIncrease()接收一个参数,代表要执行动画的目标元素 - 在forEach循环中调用函数时,传入当前遍历到的counter
- 移除函数内部
document.querySelector('.counter')的代码,改用传入的参数 - 修正
setCelebrateClass方法中引用的元素,确保操作的是当前目标元素
修改后的完整代码
<button id="button">test</button> <h2 class="counter" data-count-start="500">788</h2> <h2 class="counter" data-count-start="333">666</h2> <script> let test = true; const button = document.getElementById('button'); button.addEventListener('click', function() { if (test) { document.querySelectorAll('.counter').forEach(counter => { numberIncrease(counter); // 传入当前遍历的元素 }) } else { console.log('no'); } }) // 接收目标元素作为参数 function numberIncrease(targetCounter) { const tl = gsap.timeline(); const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; animateCount(targetCounter); // 传递目标元素 function animateCount(el) { const start = removeCommas(el.dataset.countStart); const end = removeCommas(el.textContent); tl.fromTo( el, { innerText: start, "--font-variation-weight": 300, scale: reducedMotion ? 1 : 0.99 }, { innerText: end, snap: { innerText: 1 }, duration: reducedMotion ? 0 : 3, ease: "linear", onUpdate: () => { el.innerHTML = formatNumber(el.innerText); } } ).to(el, { scale: 1, "--font-variation-weight": 600, ease: "elastic.out(1, 0.2)", duration: 1.2 }); } function celebrate() { setCelebrateClass(true); } // 操作传入的目标元素 function setCelebrateClass(enabled) { targetCounter.classList.toggle("celebrate", enabled); } function removeCommas(num) { return num.replace(/,/g, ""); } function formatNumber(num) { return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,"); } } </script>
内容的提问来源于stack exchange,提问作者feliel93
相关产品推荐
相关产品推荐

