一次只允许打开一个的手风琴

Accordion that allows only one open at a time

我有一个手风琴非常好用,它在网站上看起来不错并且可以正常工作。但是,我正在尝试向它添加更多 JavaScript 功能,使其看起来更专业。

目前,手风琴允许您同时打开多个面板,即如果我打开一个选项卡,然后打开另一个选项卡,两个选项卡将同时打开。关闭这些面板的唯一方法是 re-click 在 header.

我想要一些 JavaScript 代码来防止同时打开多个选项卡,因此如果我单击一个新面板,它应该首先关闭现有的打开面板。这是我的 HTML 手风琴代码:

var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
  acc[i].addEventListener("click", function() {
    this.classList.toggle("active");
    var panel = this.nextElementSibling;
    if (panel.style.maxHeight) {
      panel.style.maxHeight = null;
    } else {
      panel.style.maxHeight = panel.scrollHeight + "px";
    }
  });
}
<div class="accordion"><b>Heading 1</b></div>
<div class="panel">
  <p class="text-light">Text 1</p>
</div>
<div class="accordion"><b>Heading 2</b></div>
<div class="panel">
  <p class="text-light">Text 2</p>
</div>

  1. 为每个手风琴动态添加数据 ID div(基于 0 个数据 ID)
  2. 单击时 -> 如果索引与该数据 ID 匹配 -> 打开(显示)否则 (display:none)
  3. 我还要添加一个 isActive 标志变量:let currentOpenID = cur; 这样一来,如果您单击已经打开的手风琴元素,您 display:none 也会...

逻辑是:你需要一些东西来比较,那就是你的开放标志,否则关闭。

我也会用

panel.style.display: none;
panel.style.display: block;

您需要做的就是添加一个跟踪当前活动元素的变量。每次单击时,您的代码会将当前活动项目的最大高度更改为 0。一旦这样做,它将设置刚刚单击的项目的最大高度,并将该项目设置为当前活动的项目。

我稍微重写了您的代码,但这绝对适合您。

var acc = document.querySelectorAll(".accordion");
var active = null;

acc.forEach((item, i) => {
  item.addEventListener("click", function () {
    this.classList.toggle("active");
    var panel = this.nextElementSibling;
    
    if(active) {
      active.style.maxHeight = null;
    }
    
    if(panel !== active) {
      panel.style.maxHeight = panel.scrollHeight + "px";
      active = panel
    } else {
      active = null
    }
  });
});
.panel {
  max-height: 0;
  overflow: hidden;
}
<div class="accordion"><b>Heading 1</b></div>
<div class="panel">
   <p class="text-light">Text 1</p>
</div>
<div class="accordion"><b>Heading 2</b></div>
<div class="panel">
   <p class="text-light">Text 2</p>
</div>
<div class="accordion"><b>Heading 3</b></div>
<div class="panel">
   <p class="text-light">Text 3</p>
</div>
<div class="accordion"><b>Heading 4</b></div>
<div class="panel">
   <p class="text-light">Text 4</p>
</div>

每次单击标题时,您不仅需要切换所单击项目的状态,还需要遍历其他手风琴项目并关闭它们。在下面的代码中,我稍微简化了您的示例并使用 display: none; 隐藏手风琴项:

const accordions = Array.from(document.getElementsByClassName("accordion"));
accordions.forEach(accordion1 =>
  accordion1.addEventListener("click", () =>
    accordions.forEach(accordion2 =>
      accordion2.nextElementSibling.classList.toggle(
        "hidden",
        accordion1 !== accordion2 ||
        !accordion1.nextElementSibling.classList.contains("hidden")
      )
    )
  )
);
.panel.hidden {
  display: none;
}
<div class="accordion"><b>Heading 1</b></div>
<div class="panel hidden">Panel 1</div>
<div class="accordion"><b>Heading 2</b></div>
<div class="panel hidden">Panel 2</div>
<div class="accordion"><b>Heading 3</b></div>
<div class="panel hidden">Panel 3</div>

我最近确实学到了一些东西...当您需要在具有相同父节点的多个节点上侦听事件时,最佳实践之一是使用事件委托,也就是说,侦听用于点击父节点。

那么您可能需要与

  • 中包含的其他链接进行交互。为了处理这个问题,closest 方法将帮助您 selecting 一个共同的父节点(参见 https://developer.mozilla.org/en-US/docs/Web/API/Element/closest ),然后 select 通过使用 querySelectorAll(或者 getElementsByClassName,如果你愿意的话)节点

    您仍然需要从这里管理您的动画,但我认为它会对您有所帮助。

    // DOM here
    let nav = document.querySelector(".nav");
    
    // Handlers here
    const clickHandler = function (e) {
      if (e.target.classList.contains("nav__link")) {
        const link = e.target;
        const siblings = link.closest(".nav").querySelectorAll(".nav__link");
    
        link.classList.toggle("active");
    
        // removes all actives except for the clicked one
        siblings.forEach((el) => {
          if (el !== link) el.classList.remove("active");
        });
      }
    };
    
    // Listeners here
    nav.addEventListener("click", clickHandler);
    
    body {
      font-family: sans-serif;
    }
    
    .nav__link {
      display: block;
      width: 100%;
    }
    .active {
      background: #0f0;
    }
    
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
      </head>
    
      <body>
        <div class="nav">
          <ul class="nav__links">
                    <li class="nav__item">
                        <a class="nav__link" href="#section--1">Section 1</a>
                    </li>
                    <li class="nav__item">
                        <a class="nav__link" href="#section--2">Section 2</a>
                    </li>
                    <li class="nav__item">
                        <a class="nav__link" href="#section--3">Section 3</a>
                    </li>
                    <li class="nav__item">
                        <a class="nav__link" href="#section--4"
                            >Section 4</a
                        >
                    </li>
                </ul>
          </div>
        </div>
    
        <script src="src/index.js"></script>
      </body>
    </html>
    

    如果你想更进一步,它看起来像这样:

    // DOM here
    let nav = document.querySelector(".nav");
    
    // Handlers here
    const clickHandler = function (e) {
      if (e.target.classList.contains("nav__link")) {
        const link = e.target; // clicked link
    
        const siblings = link.closest(".nav").querySelectorAll(".nav__link");
    
        link.classList.toggle("active");
        link.children[0].classList.toggle("hidden");
    
        // removes all actives except for the clicked one
        siblings.forEach((el) => {
          if (el !== link) {
            el.classList.remove("active");
            el.children[0].classList.add("hidden");
          }
        });
      }
    };
    
    // Listeners here
    nav.addEventListener("click", clickHandler);
    
    body {
      font-family: sans-serif;
    }
    
    .nav__link {
      display: block;
      width: 100%;
      transition: all 0.3s;
    }
    .active {
      background: #0f0;
    }
    
    .hidden {
      display: none;
    }
    
        <div class="nav">
          <ul class="nav__links">
                    <li class="nav__item">
                        <a class="nav__link" href="#section--1">Section 1
                            <ul class="hidden">
                                <li>list item</li>
                                <li>list item</li>
                            </ul>
                        </a>
                    </li>
                    <li class="nav__item">
                        <a class="nav__link" href="#section--2">Section 2
                                <ul class="hidden">
                                        <li>list item</li>
                                        <li>list item</li>
                                    </ul>
                        </a> 
                    </li>
                    <li class="nav__item">
                        <a class="nav__link" href="#section--3">Section 3
                                <ul class="hidden">
                                        <li>list item</li>
                                        <li>list item</li>
                                    </ul>
                        </a>
                    </li>
                    <li class="nav__item">
                        <a class="nav__link" href="#section--4">section 4
                            <ul class="hidden">
                                    <li>list item</li>
                                    <li>list item</li>
                                </ul>
                                </a>
                    </li>
                </ul>
          </div>
        </div>