Аккордеон на чистом JS

Давайте разберемся как сделать аккордеон на ванильном JavaScript.


HTML

<dl>
 <dt class="question js-accordion">
  <button class="question__trigger" type="button">First Question?
 </button>
 </dt>
 <dd class="answer">
  <div>
   Lorem ipsum dolor sit amet consectetur adipisicing elit. Id, itaque quisquam? Quia cum alias in, beatae soluta dicta fuga corrupti magni? Alias minus nostrum qui at corporis, magni optio ipsam!
  </div>
 </dd>
 <dt class="question js-accordion">
  <button class="question__trigger" type="button">Second Question?</button>
 </dt>
 <dd class="answer">
  <div class="answer__content">
   Lorem ipsum dolor sit amet consectetur adipisicing elit. Id, itaque quisquam? Quia cum alias in, beatae soluta dicta fuga corrupti magni? Alias minus nostrum qui at corporis, magni optio ipsam!
  </div>
 </dd>
</dl>
Что сразу бросается в глаза, так это теги dl, dt, dd. Почему нельзя просто везде использовать div?

В принципе, можно, но аккордеон — это хороший пример списка из терминов и определений, для чего эти теги и предназначены.


Разберем по частям:

<dt class="question js-accordion">
 <button class="question__trigger" type="button">First Question?
 </button>
</dt>

В dt помещаем вопрос, но для чего оборачивать его в кнопку? Опять семантика: когда пользователь кликает на элемент — что-то должно произойти, для этого необходимо использовать button.

Вторая причина — доступность через клавиатуру: на аккордеон можно попасть через tab и раскрыть через пробел.

<dd class="answer">
 <div>
  Lorem ipsum dolor sit amet consectetur adipisicing elit. Id, itaque quisquam? Quia cum alias in, beatae soluta dicta fuga corrupti magni? Alias minus nostrum qui at corporis, magni optio ipsam!
 </div>
</dd>

В dd кладем ответ, обернутый в дополнительный div?

Дело в том, что для того чтобы анимировать «раскрытие» аккордеона, необходимо знать высоту ответа.

.answer {
 overflow: hidden;
 height: 0;
 transition: height 0.5s;
}

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

Как только аккордеон будет раскрыт — мы присвоим answer ранее измеренную высоту.

JavaScript

Найдем все аккордеоны на странице и инициализируем их:

const elements = [...document.querySelectorAll('.js-accordion')];
elements.forEach(accordion);

Воспользуемся замыканием:

function accordion(element) {
 // объект, в котором будем хранить всю необходимую информацию
 const instance = {};
 function init() {
  // найдем вопрос и ответ
  findElements(instance, element);
  // измерим высоту ответа
  measureHeight(instance);
  // добавим логику нажатия на кнопку
  subscribe(instance);
 }
 init();
}

findElements

function findElements(object, element) {
 const instance = object;
 // element - это "вопрос", по которому происходит нажатие
 instance.element = element;
 // target - это "ответ", который должен "раскрываться"
instance.target = element.nextElementSibling;
}
Если вспомнить разметку — ответ всегда идет следом за вопросом, поэтому мы и используем свойство nextElementSibling.


measureHeight

function measureHeight(object) {
 const instance = object;
 // вычисляем высоту ответа
 instance.height = object.target.firstElementChild.clientHeight;
}
В то время, как target имеет нулевую высоту, у его потомка размер остался тем же. Этот самый размер мы и сохраняем.subscribe

Аккордеон на чистом JS Аккордеон, Javascript, Frontend, Web, IT, HTML, Программирование, Web-программирование, Гифка, Длиннопост

У answer высота — 0, а у answer__content — нет

subscribe

function subscribe(instance) {
 instance.element.addEventListener('click', (event) => {
  // отменяем "действие по умолчанию"
  event.preventDefault();
  // меняем состояние аккордеона
  changeElementStatus(instance);
 });
 // если размер окна поменяется - измерим высоту ответа заново
 window.addEventListener('resize', () => measureHeight(instance));
}

changeElementStatus

function changeElementStatus(instance) {
 if (instance.isActive) {
  hideElement(instance);
 } else {
  showElement(instance);
 }
}
Если аккордеон активен — сворачиваем его, иначе — раскрываем.


hideElement и showElement

function hideElement(object) {
 const instance = object;
 const { target } = instance;
 // обнуляем высоту ответа
 target.style.height = null;
 // делаем статус неактивным
 instance.isActive = false;
}
function showElement(object) {
 const instance = object;
 const { target, height } = instance;
 // задаем ответу сохраненную в measureHeight высоту
 target.style.height = `${height}px`;
 // делаем статус активным
 instance.isActive = true;
}

Аккордеон на чистом JS Аккордеон, Javascript, Frontend, Web, IT, HTML, Программирование, Web-программирование, Гифка, Длиннопост

Как всегда — демо на codepen.