Аккордеон, который позволяет открывать только один раз за раз

#javascript #accordion

Вопрос:

У меня есть аккордеон, который работает очень хорошо, он хорошо смотрится на сайте и работает так, как должен. Тем не менее, я пытаюсь добавить в него дополнительные функции JavaScript, чтобы он выглядел более профессионально.

В настоящее время аккордеон позволяет открывать несколько панелей одновременно, т. е. Если я открою одну вкладку, а затем открою другую вкладку, обе вкладки будут открыты одновременно. И единственный способ закрыть эти панели-это повторно щелкнуть по заголовку.

Мне бы хотелось, чтобы какой-нибудь код 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. code-boxx.com/simple-vanilla-javascript-accordion

Ответ №1:

Действительно, кое-что я узнал совсем недавно… Одна из лучших практик, когда вам нужно прослушать событие на нескольких узлах, имеющих одного и того же родителя, — это использовать делегирование событий, то есть прослушивать щелчок на родительском узле.

Затем вам, возможно, потребуется взаимодействовать с другими ссылками, завернутыми в

  • . Чтобы справиться с этим, ближайший метод поможет вам выбрать общего родителя ( см. https://developer.mozilla.org/en-US/docs/Web/API/Element/closest ), а затем выберите узлы с помощью 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> 
  • Ответ №2:

    1. динамически добавляйте идентификатор данных в каждый раздел аккордеона (идентификаторы данных на основе 0)
    2. при нажатии -> если индекс соответствует этим данным-идентификатор ->> открыть (отобразить) еще (отображение:нет)
    3. Я бы также добавил переменную флага isActive : пусть currentOpenID = cur; так что, если вы нажмете на уже открытый элемент аккордеона, вы отобразите:этого тоже нет…

    логика такова: вам нужно с чем-то сравнить, это будет ваш флаг открытия, а не закрытия.

    Я бы также использовал

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

    Ответ №3:

    Все, что вам нужно сделать, это добавить переменную, которая отслеживает текущий активный элемент. При каждом щелчке ваш код изменит максимальную высоту текущего активного элемента на 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> 

    Ответ №4:

    Каждый раз, когда вы нажимаете на заголовок, вам нужно не только переключать состояние нажатого элемента, но и переключаться между другими элементами аккордеона и закрывать их. В приведенном ниже коде я немного упростил ваш пример и использовал 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>