addEventListener设为once仍多次触发,致应用动画异常及事件绑定问题
问题:多次点击添加提示按钮后,关闭按钮事件重复触发且无法正确移除
点击添加提示按钮(addTodoBtnLm)时,会为提示框的关闭按钮(closePromptBtn)绑定点击事件监听器。单次操作时关闭按钮的监听器可正常移除,但多次点击添加提示按钮后,监听器无法被正确移除;即使设置{once: true},点击关闭按钮时事件仍会多次触发(控制台多次输出'wrapper'),导致应用动画异常。
代码示例
const addTodoBtnLm = document.getElementById('todo-app-intro__add-btn'); const refreshQuoteBtn = document.getElementById('quote__btn'); const timsIntroBtns = {}; function checkActiveBtn(btnLm) { if (btnLm.getAttribute('aria-expanded') === 'false') { btnLm.classList.add('btn--active'); } else { btnLm.classList.remove('btn--active'); } }; function showPrompt(hideTmId, promptLm, btnLm) { clearTimeout(hideTmId); promptLm.removeAttribute('hidden'); btnLm.setAttribute('aria-expanded', true); setTimeout(() => { promptLm.classList.add('todo-app-prompt--active'); }, 0); } function setHideTim(timeoutId, promptLm) { timsIntroBtns[timeoutId] = setTimeout(() => { promptLm.setAttribute('hidden', ''); }, 1500); } function hidePrompt(promptLm, btnLm, closeBtnLm, wrappingFunction) { closeBtnLm.removeEventListener('click', wrappingFunction); btnLm.setAttribute('aria-expanded', false); promptLm.classList.remove('todo-app-prompt--active'); } function addTodoPrompt() { const todoAppPromptLm = document.getElementById('todo-app-prompt'); const closePromptBtn = document.getElementById('todo-app-prompt__cancel-btn'); function wrappingFunction() { /*多次点击添加按钮后,点击关闭按钮时控制台会多次输出'wrapper',次数与添加按钮点击次数一致*/ console.log('wrapper'); checkActiveBtn(addTodoBtnLm); setHideTim('promptHideTmId2', todoAppPromptLm) hidePrompt(todoAppPromptLm, addTodoBtnLm, closePromptBtn, wrappingFunction); } checkActiveBtn(addTodoBtnLm); if (todoAppPromptLm.matches('.todo-app-prompt--active')) { setHideTim('promptHideTmId', todoAppPromptLm) //此处应移除监听器,但未生效 hidePrompt(todoAppPromptLm, addTodoBtnLm, closePromptBtn, wrappingFunction); } else { showPrompt(timsIntroBtns.promptHideTmId, todoAppPromptLm, addTodoBtnLm); clearTimeout(timsIntroBtns.promptHideTmId2); closePromptBtn.addEventListener('click', wrappingFunction); } } addTodoBtnLm.addEventListener('click', () => { addTodoPrompt(); });
已尝试方案
- 在检测提示框已打开的
if语句中调用removeEventListener,但未生效; - 为
closePromptBtn的addEventListener设置{once: true},虽可移除removeEventListener代码,但事件仍会累加触发。
问题根源
每次调用addTodoPrompt时,都会创建一个全新的wrappingFunction函数实例。removeEventListener需要与绑定事件时完全相同的函数引用才能生效,所以用新创建的函数去移除旧的监听器根本找不到,导致旧的监听器一直残留。多次点击添加按钮后,就会绑定多个独立的wrappingFunction,点击关闭按钮时所有这些函数都会被触发。
解决方案
把wrappingFunction的引用统一保存,确保绑定和移除时用的是同一个函数实例。以下是修改后的代码:
const addTodoBtnLm = document.getElementById('todo-app-intro__add-btn'); const refreshQuoteBtn = document.getElementById('quote__btn'); const timsIntroBtns = {}; // 全局保存当前的关闭按钮监听器引用 let currentCloseListener = null; function checkActiveBtn(btnLm) { if (btnLm.getAttribute('aria-expanded') === 'false') { btnLm.classList.add('btn--active'); } else { btnLm.classList.remove('btn--active'); } }; function showPrompt(hideTmId, promptLm, btnLm) { clearTimeout(hideTmId); promptLm.removeAttribute('hidden'); btnLm.setAttribute('aria-expanded', true); setTimeout(() => { promptLm.classList.add('todo-app-prompt--active'); }, 0); } function setHideTim(timeoutId, promptLm) { timsIntroBtns[timeoutId] = setTimeout(() => { promptLm.setAttribute('hidden', ''); }, 1500); } function hidePrompt(promptLm, btnLm, closeBtnLm) { // 使用统一引用移除监听器 if (currentCloseListener) { closeBtnLm.removeEventListener('click', currentCloseListener); currentCloseListener = null; } btnLm.setAttribute('aria-expanded', false); promptLm.classList.remove('todo-app-prompt--active'); } function addTodoPrompt() { const todoAppPromptLm = document.getElementById('todo-app-prompt'); const closePromptBtn = document.getElementById('todo-app-prompt__cancel-btn'); // 定义监听器函数 function wrappingFunction() { console.log('wrapper'); checkActiveBtn(addTodoBtnLm); setHideTim('promptHideTmId2', todoAppPromptLm) hidePrompt(todoAppPromptLm, addTodoBtnLm, closePromptBtn); } checkActiveBtn(addTodoBtnLm); if (todoAppPromptLm.matches('.todo-app-prompt--active')) { setHideTim('promptHideTmId', todoAppPromptLm) hidePrompt(todoAppPromptLm, addTodoBtnLm, closePromptBtn); } else { showPrompt(timsIntroBtns.promptHideTmId, todoAppPromptLm, addTodoBtnLm); clearTimeout(timsIntroBtns.promptHideTmId2); // 先移除旧监听器,再绑定新的 if (currentCloseListener) { closePromptBtn.removeEventListener('click', currentCloseListener); } currentCloseListener = wrappingFunction; closePromptBtn.addEventListener('click', currentCloseListener); } } addTodoBtnLm.addEventListener('click', () => { addTodoPrompt(); });
关键修改点
- 新增
currentCloseListener变量统一保存监听器引用; - 在
hidePrompt中使用该引用移除监听器,避免无效移除; - 绑定新监听器前先移除旧的,防止重复绑定;
- 移除
hidePrompt的wrappingFunction参数,直接使用全局保存的引用。
这样无论点击多少次添加按钮,关闭按钮始终只会绑定一个有效监听器,点击关闭时只会触发一次,动画也能正常执行。
内容的提问来源于stack exchange,提问作者Angel
相关产品推荐
相关产品推荐

