为何将click事件从dropdown改为select后,菜单关闭功能恢复正常?
我有一个带有menu类的div,内部包含选项列表,默认设置display:none。当用户点击时,程序会添加menu-open类,使菜单可见。我的需求是用户选择选项后立即关闭菜单,计划通过点击事件移除menu-open类,让元素恢复display:none,但最初实现时该功能失效。我将JS代码第12行的dropdown.addEventListener("click", () => {...})改为select.addEventListener("click", () => {...})后问题解决,但无法解释该行为,希望有人能说明原因。
相关代码
JavaScript
const dropdowns = document.querySelectorAll(".dropdown"); // Loop through all dropdown elements dropdowns.forEach((dropdown) => { const select = dropdown.querySelector(".select"); const caret = dropdown.querySelector(".caret"); const menu = dropdown.querySelector(".menu"); const options = dropdown.querySelectorAll(".menu li"); const selected = dropdown.querySelector(".selected"); dropdown.addEventListener("click", () => { menu.classList.toggle("menu-open"); options.forEach((option) => { option.addEventListener("click", () => { menu.classList.remove("menu-open"); options.forEach((option) => { option.classList.remove("active"); }); }); }); }); });
CSS
/*Global settings*/ html { font-size: 62.5%; } *, ::after, ::before { margin: 0; padding: 0; box-sizing: border-box; } /*-------------------------------------------------------------*/ body { font-family: "Roboto"; background-color: #23242a; color: #fff; height: 100vh; display: flex; justify-content: center; align-items: center; } .dropdown { margin: 4rem; position: relative; } .select { min-width: 15rem; border-radius: 0.7rem; background-color: #2a2f3b; padding: 1rem; display: flex; justify-content: space-between; cursor: pointer; } .select:hover { background-color: #323741; } .caret { transition: 0.3s; } .menu { background-color: #323741; padding: 0.5rem 0.5rem; border-radius: 0.7rem; position: absolute; top: 4rem; width: 100%; cursor: pointer; display: none; } .menu li { border-radius: 0.2rem; list-style-type: none; padding: 0.3rem 0.8rem; margin: 0.3rem; } .menu li:hover { background-color: #2a2d35; } .active { background-color: #23242a; } /*Clicked Styles (Added later in JS) */ .caret-rotate { transform: rotate(180deg); } .menu-open { display: block; opacity: 1; }
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;400;700&display=swap" rel="stylesheet" /> <link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" /> <title>Drop-down</title> </head> <body> <div class="dropdown"> <div class="select"> <span class="selected">Figma</span> <div class="caret"><i class="fa-solid fa-caret-down"></i></div> </div> <ul class="menu"> <li>Framer</li> <li>Sketch</li> <li>Invision Studio</li> <li class="active">Figma</li> <li>Adobe XD</li> </ul> </div> <div class="dropdown"> <div class="select"> <span class="selected">Facebook</span> <div class="caret"><i class="fa-solid fa-caret-down"></i></div> </div> <ul class="menu"> <li class="active">FaceBook</li> <li>instagram</li> <li>Twitter</li> <li>Linkedin</li> <li>Tik Tok</li> </ul> </div> </body> <script src="app.js"></script> </html>
原因解释
核心问题是事件冒泡和toggle方法的执行逻辑冲突:
原代码(绑定dropdown点击事件)的问题:
当你点击菜单里的<li>选项时,事件会沿着DOM树向上冒泡,先执行<li>自身的点击事件(移除menu-open类,菜单隐藏),然后事件冒泡到父元素.dropdown,触发绑定在它身上的点击事件,里面的menu.classList.toggle("menu-open")会再次执行——这时候因为刚移除了类,toggle会重新添加menu-open,导致菜单立刻又显示出来,看起来就是关闭功能失效。
另外还有一个隐藏问题:每次点击.dropdown都会给所有<li>重新绑定点击事件,多次点击后同一个选项会绑定多个事件,触发时会重复执行移除操作,属于代码冗余。修改后(绑定select点击事件)的解决原理:
看HTML结构,.menu是.dropdown的直接子元素,但和.select是同级关系,不属于.select的子元素。所以点击<li>时,事件只会冒泡到.menu和.dropdown,不会触发.select的点击事件。这时候<li>的点击事件移除menu-open后,没有后续的toggle操作把类加回来,菜单就会保持隐藏状态,符合预期。
内容的提问来源于stack exchange,提问作者Salvarez

