删除HTML中accordion__content div后手风琴失效 纯JS求解
嵌套手风琴(多级导航)组件修复方案
故障原因
原代码存在3个强依赖问题,删除accordion__content节点后会直接抛出JS错误导致全组件失效:
- 初始化时全局获取所有
accordion__content节点生成静态集合,节点数量和菜单项数量不匹配时,循环访问会出现undefined - 硬编码使用
parentEl.children[1]访问内容节点,默认内容区永远是菜单项的第二个子元素,结构变动就会取错节点或者取不到 - 所有对内容区的样式操作都没做存在性校验,访问undefined的style属性直接抛错中断执行
- 原代码click事件逻辑中存在多余的孤立字符
c,本身就有语法错误
修复后完整代码
HTML结构
支持任意菜单项删除accordion__content节点,组件不会崩溃,无下级内容的菜单项点击/hover不会触发展开动作
<div class="accordion"> <div class="accordion--item"> <h4 class="accordion__title"> Parent <span class="accordion__icon"> <span class="line-01"></span> <span class="line-02"></span> </span> </h4> <div class="accordion__content"> <div class="accordion--item accordion--nested"> <h4 class="accordion__title"> Child(有子内容) <span class="accordion__icon"> <span class="line-01"></span> <span class="line-02"></span> </span> </h4> <div class="accordion__content"> Child of child </div> </div> <div class="accordion--item accordion--nested"> <h4 class="accordion__title"> Child(无子内容,删除content节点仍正常显示) </h4> <!-- 此处删除了accordion__content div,组件运行不受影响 --> </div> <div class="accordion--item accordion--nested"> <h4 class="accordion__title"> Child 2 <span class="accordion__icon"> <span class="line-01"></span> <span class="line-02"></span> </span> </h4> <div class="accordion__content"> <div class="accordion--item accordion--nested"> <h4 class="accordion__title"> Child of child 2 <span class="accordion__icon"> <span class="line-01"></span> <span class="line-02"></span> </span> </h4> <div class="accordion__content"> Child of child 2 </div> </div> </div> </div> </div> </div> </div>
CSS样式
/** * 核心必需样式 */ .accordion__title { cursor: pointer; margin: 0; position: relative; } .accordion__icon { position: absolute; top: 50%; right: 24px; transform: translateY(-50%); } .accordion__icon .line-01, .accordion__icon .line-02 { position: absolute; top: 50%; left: 50%; width: 16px; height: 2px; background-color: #272343; transition: 0.3s; } .accordion__icon .line-02 { transform: rotate(90deg); } .accordion__content { display: none; } .accordion--open > .accordion__title .line-02 { transform: rotate(0deg); } /* 无子项的标题自动隐藏展开图标 */ .accordion--item:not(:has(.accordion__content)) .accordion__icon { display: none; } /* 核心样式结束 */ /** * 视觉美化样式 */ body { font-family: "Rubik", sans-serif; max-width: 768px; margin: 0 auto; color: #111; overflow-y: scroll; } .accordion { list-style: none; padding: 0; margin: 0; } .accordion--item { position: relative; } .accordion__title { padding: 20px 16px; font-size: 16px; transition: 0.2s; margin: 0; } .accordion__content { padding: 0 16px; background: #f5f5f7; } .accordion__content p { margin: 0 0 16px; } .accordion__title:hover { background-color: #433d6f; color: #fff; } .accordion__title:hover .line-01, .accordion__title:hover .line-02 { background-color: #fff; } .accordion--open > .accordion__title { background-color: #272343; color: #fff; } .accordion--open > .accordion__title .line-01, .accordion--open > .accordion__title .line-02 { background-color: #fff; } .accordion--nested .accordion__title { padding: 16px 16px 16px 32px; font-size: 14px; } .accordion--nested .accordion--nested .accordion__title { padding-left: 48px; }
原生JavaScript逻辑
function initAccordion() { const accordionItems = document.querySelectorAll(".accordion--item"); const isDesktop = window.innerWidth > 1000; // 清除旧事件,避免重复绑定 accordionItems.forEach(item => { const newItem = item.cloneNode(true); item.parentNode.replaceChild(newItem, item); }); document.querySelectorAll(".accordion--item").forEach(item => { const openHandler = (e) => { e.stopPropagation(); const currentContent = item.querySelector(":scope > .accordion__content"); // 无下级内容直接返回,不执行展开 if (!currentContent) return; // 关闭非当前分支的所有打开菜单 document.querySelectorAll(".accordion--item.accordion--open").forEach(openItem => { if (item.contains(openItem) || openItem.contains(item)) return; openItem.classList.remove("accordion--open"); const openContent = openItem.querySelector(":scope > .accordion__content"); if (openContent) openContent.style.display = "none"; }); // 展开当前项 item.classList.add("accordion--open"); currentContent.style.display = "block"; // 展开所有父级菜单 let parentItem = item.parentElement.closest(".accordion--item"); while (parentItem) { parentItem.classList.add("accordion--open"); const parentContent = parentItem.querySelector(":scope > .accordion__content"); if (parentContent) parentContent.style.display = "block"; parentItem = parentItem.parentElement.closest(".accordion--item"); } }; const closeHandler = (e) => { e.stopPropagation(); // 鼠标移入子菜单时不触发关闭 if (item.contains(e.relatedTarget)) return; item.classList.remove("accordion--open"); const currentContent = item.querySelector(":scope > .accordion__content"); if (currentContent) currentContent.style.display = "none"; // 关闭所有子级菜单 item.querySelectorAll(".accordion--item.accordion--open").forEach(childItem => { childItem.classList.remove("accordion--open"); const childContent = childItem.querySelector(":scope > .accordion__content"); if (childContent) childContent.style.display = "none"; }); }; if (isDesktop) { item.addEventListener("mouseenter", openHandler); item.addEventListener("mouseleave", closeHandler); } else { item.addEventListener("click", openHandler); } }); } // 页面加载完成初始化 document.addEventListener("DOMContentLoaded", initAccordion); // 窗口尺寸变化时重新初始化,自动切换hover/click交互 window.addEventListener("resize", () => { clearTimeout(window.resizeTimer); window.resizeTimer = setTimeout(initAccordion, 200); });
修复说明
- 所有DOM查询改为事件触发时实时查找,使用
:scope > .accordion__content只匹配当前项的直接子内容区,不会误查嵌套的子菜单内容 - 所有内容区操作前都加存在性判断,没有内容区的菜单项不会执行后续逻辑,不会抛出JS错误
- 弃用硬编码的
children[1]索引访问,改用closest()方法查找父级菜单项,DOM结构变动不受影响 - 修复原代码hover时父级菜单提前关闭、同分支菜单互相强制关闭的问题
- 新增窗口resize防抖监听,切换桌面/移动端时自动切换交互模式
- 无子内容的菜单项自动隐藏展开图标,交互逻辑更符合多级导航的使用习惯
内容的提问来源于stack exchange,提问作者Jovan Jovanovic
相关产品推荐
相关产品推荐

