currentPage的animationend事件重复触发Bug求助
问题:animationend事件重复触发的排查与解决
问题描述
点击箭头会调用updateArrows函数,最终执行next()更新步骤。向下切换到步骤4时一切正常,但从步骤4向上切回步骤2时,currentPage.addEventListener("animationend",function (e) { })会触发两次,控制台先后输出正确的步骤2和旧的步骤4。推测是事件重复绑定,但无法理解为何前期无此问题。
截图


相关代码
const changeStep = document.querySelectorAll(".step"); const currentPaginate = document.querySelector(".pagination span.active"); const arrows = document.querySelectorAll(".arrow"); let isAnimating = false; for (let arrow of arrows) arrow.addEventListener("click", updateArrows); for (let step of changeStep) step.addEventListener("click", next); function updateArrows(e, currentStep = null) { if (isAnimating) return false; let arrow = null; e.target ? (arrow = e.target) : (arrow = e); let nextStep; if (currentStep == null) { currentStep = document.querySelector(".step.current"); if (arrow.classList.contains("arrow-bottom")) { nextStep = currentStep.nextElementSibling; } else { nextStep = currentStep.previousElementSibling; } } else { nextStep = document.querySelector(".step.current"); } if (!arrow.classList.contains("impossible")) { if (nextStep.dataset.id != 1 && nextStep.dataset.id != 4) { arrows.forEach(function (arrow) { if (arrow.classList.contains("impossible")) { arrow.classList.remove("impossible"); } }); } else if (nextStep.dataset.id == 4) { if (arrow.previousElementSibling.classList.contains("impossible")) arrow.previousElementSibling.classList.remove("impossible"); arrow.classList.add("impossible"); } else if (nextStep.dataset.id == 1) { if (arrow.nextElementSibling.classList.contains("impossible")) arrow.nextElementSibling.classList.remove("impossible"); arrow.classList.add("impossible"); } if (e.target != undefined) next(nextStep); } } function next(e) { if (isAnimating) { return false; } isAnimating = true; let step = null; const currentPage = document.querySelector(".page.current"); e.target ? (step = e.target) : (step = e); if (!step.classList.contains("current")) { const currentStep = document.querySelector(".step.current"); const nextStep = step.dataset.id; const nextPage = document.querySelector('[data-id="' + nextStep + '"]'); currentStep.classList.remove("current"); step.classList.add("current"); currentPaginate.textContent = "0" + nextStep; let arrow; if (currentStep.dataset.id < nextStep) { arrow = document.querySelector(".arrow-bottom"); } else { arrow = document.querySelector(".arrow-top"); } currentPage.addEventListener( "animationend", function (e) { e = window.event || e; if (this === e.target) { currentPage.classList.remove("current", "moveToBottomFade"); nextPage.classList.add("current"); console.log(nextPage); isAnimating = false; } }, true ); currentPage.classList.add("moveToBottomFade"); } }
问题根源
每次调用next()时,都会给当前的currentPage绑定一次animationend事件,但从未移除已绑定的监听:
- 向下切换时,每次绑定事件的是不同的
currentPage(比如从1→2绑定page1,2→3绑定page2),旧页面的事件监听不会干扰新切换,所以前期无问题; - 回退时,比如从4→3给page4绑定了事件,3→2给page3绑定事件,此时page4的监听仍存在。后续如果page4的动画相关状态变化触发了
animationend(或因捕获模式true导致事件冒泡时被触发),就会执行旧的监听逻辑,导致重复输出。
解决方法
方法1:使用once自动移除监听(推荐)
修改addEventListener的第三个参数为配置对象,设置once: true,事件触发一次后会自动移除监听:
currentPage.addEventListener( "animationend", function (e) { e = window.event || e; if (this === e.target) { currentPage.classList.remove("current", "moveToBottomFade"); nextPage.classList.add("current"); console.log(nextPage); isAnimating = false; } }, { capture: true, once: true } // 替换原布尔值为配置对象 );
方法2:命名函数+手动移除监听
将匿名事件处理函数改为命名函数,触发后手动移除监听:
function handlePageAnimationEnd(e) { e = window.event || e; if (this === e.target) { currentPage.classList.remove("current", "moveToBottomFade"); nextPage.classList.add("current"); console.log(nextPage); isAnimating = false; // 移除当前元素的事件监听 currentPage.removeEventListener("animationend", handlePageAnimationEnd, true); } } // 绑定事件 currentPage.addEventListener("animationend", handlePageAnimationEnd, true);
方法3:绑定前清理旧监听
如果需要保留多次触发的可能,可在绑定新监听前先移除旧的:
// 先移除可能存在的旧监听 currentPage.removeEventListener("animationend", handlePageAnimationEnd, true); // 再绑定新监听 currentPage.addEventListener("animationend", handlePageAnimationEnd, true);
内容的提问来源于stack exchange,提问作者Yann
相关产品推荐
相关产品推荐

