如何阻止按钮子元素触发mouseenter事件并正确移除事件?
按钮内部图片触发mouseenter重复执行及事件无法移除的解决方法
问题背景
按钮内部包含图片时,hover操作会同时触发按钮和图片的mouseenter事件,导致音效重复播放;同时现有两种JS写法都无法在按钮禁用后成功移除mouseenter事件。
相关代码
HTML(核心按钮结构)
<div class="catalogue-hidden"> <div id="product-1" class="product estusFlask"> <img src="./products/Item_Estus_Flask.png" /> <span>Estus Flask</span> <button id="btn-1" class="button"> 1000 <img src="./icons/soul_of_a_proud_paladin.png" /> </button> </div> <!-- 其他产品按钮结构类似 --> </div>
现有JS写法问题
写法一(捕获阶段绑定)
function selectSfx() { var audio = document.querySelector(".selectSfx"); audio.currentTime = 0; audio.play(); } for (let idNumber = 1; idNumber < 13; idNumber++) { setTimeout(() => { document .getElementById(`btn-${idNumber}`) .addEventListener("mouseenter", selectSfx, true); document .getElementById(`btn-${idNumber}`) .addEventListener("click", (e) => { condition++; okSfx(); document.getElementById(`product-${idNumber}`).className = "product-fake"; document.getElementById(`btn-${idNumber}`).disabled = true; // 移除事件失败,需严格匹配捕获阶段参数 document .getElementById(`btn-${idNumber}`) .removeEventListener("mouseenter", selectSfx, true); }); }, 4000); }
- 优点:通过捕获阶段绑定避免了图片触发重复事件
- 问题:移除事件时需严格匹配第三个参数
true,且可能因元素加载时机问题失效
写法二(匿名函数绑定)
function selectSfx() { var audio = document.querySelector(".selectSfx"); audio.currentTime = 0; audio.play(); } for (let idNumber = 1; idNumber < 14; idNumber++) { setTimeout(() => { document .getElementById(`btn-${idNumber}`) .addEventListener("mouseenter", (e) => { selectSfx(); }); }, 4000); }
- 优点:同样避免了图片重复触发
- 问题:匿名函数无法被
removeEventListener识别,无法移除事件
解决方案
1. 从根源解决重复触发问题:CSS禁用图片鼠标事件
给按钮内部的图片添加pointer-events: none,让图片不响应任何鼠标事件,hover图片时只会触发按钮的mouseenter:
.button img { pointer-events: none; }
2. 修复事件无法移除问题:使用命名函数+存储引用
避免使用匿名函数,将每个按钮的事件处理函数引用存储起来,确保移除时能精准匹配:
function selectSfx() { const audio = document.querySelector(".selectSfx"); audio.currentTime = 0; audio.play(); } // 存储每个按钮的事件处理函数引用 const buttonHandlers = {}; for (let idNumber = 1; idNumber < 13; idNumber++) { setTimeout(() => { const btn = document.getElementById(`btn-${idNumber}`); if (!btn) return; // 确保元素已加载 // 绑定命名函数 buttonHandlers[idNumber] = selectSfx; btn.addEventListener("mouseenter", buttonHandlers[idNumber]); btn.addEventListener("click", () => { condition++; console.log(condition); okSfx(); document.getElementById(`product-${idNumber}`).className = "product-fake"; btn.disabled = true; // 精准移除事件 btn.removeEventListener("mouseenter", buttonHandlers[idNumber]); if (condition >= 11) { document.querySelector(".product-hidden").className = "product"; document.getElementById("btn-12").className = "button"; } }); }, 4000); }
3. 更优方案:事件委托
将事件绑定到父元素,无需逐个绑定/移除事件,且自动支持动态生成的元素:
function selectSfx() { const audio = document.querySelector(".selectSfx"); audio.currentTime = 0; audio.play(); } // 委托mouseenter事件到父容器 document.querySelector(".catalogue-hidden").addEventListener("mouseenter", (e) => { const btn = e.target.closest(".button"); // 仅处理未禁用的按钮 if (btn && !btn.disabled) { selectSfx(); } }); // 委托click事件到父容器 document.querySelector(".catalogue-hidden").addEventListener("click", (e) => { const btn = e.target.closest(".button"); if (!btn || btn.disabled) return; const idNumber = btn.id.split("-")[1]; condition++; console.log(condition); okSfx(); document.getElementById(`product-${idNumber}`).className = "product-fake"; btn.disabled = true; if (condition >= 11) { document.querySelector(".product-hidden").className = "product"; document.getElementById("btn-12").className = "button"; } });
- 优势:无需手动移除事件,通过
!btn.disabled判断自动禁用hover音效;代码更简洁,性能更优。
内容的提问来源于stack exchange,提问作者Guchierrez
相关产品推荐
相关产品推荐

