You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

删除HTML中accordion__content div后手风琴失效 纯JS求解

嵌套手风琴(多级导航)组件修复方案

故障原因

原代码存在3个强依赖问题,删除accordion__content节点后会直接抛出JS错误导致全组件失效:

  1. 初始化时全局获取所有accordion__content节点生成静态集合,节点数量和菜单项数量不匹配时,循环访问会出现undefined
  2. 硬编码使用parentEl.children[1]访问内容节点,默认内容区永远是菜单项的第二个子元素,结构变动就会取错节点或者取不到
  3. 所有对内容区的样式操作都没做存在性校验,访问undefined的style属性直接抛错中断执行
  4. 原代码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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 12:36:19