Аккордеон на чистом JS
Давайте разберемся как сделать аккордеон на ванильном JavaScript.
HTML
<dl>Что сразу бросается в глаза, так это теги dl, dt, dd. Почему нельзя просто везде использовать div?
<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>
В принципе, можно, но аккордеон — это хороший пример списка из терминов и определений, для чего эти теги и предназначены.
Разберем по частям:
<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) {findElements
// объект, в котором будем хранить всю необходимую информацию
const instance = {};
function init() {
// найдем вопрос и ответ
findElements(instance, element);
// измерим высоту ответа
measureHeight(instance);
// добавим логику нажатия на кнопку
subscribe(instance);
}
init();
}
function findElements(object, element) {Если вспомнить разметку — ответ всегда идет следом за вопросом, поэтому мы и используем свойство nextElementSibling.
const instance = object;
// element - это "вопрос", по которому происходит нажатие
instance.element = element;
// target - это "ответ", который должен "раскрываться"
instance.target = element.nextElementSibling;
}
measureHeight
function measureHeight(object) {В то время, как target имеет нулевую высоту, у его потомка размер остался тем же. Этот самый размер мы и сохраняем.subscribe
const instance = object;
// вычисляем высоту ответа
instance.height = object.target.firstElementChild.clientHeight;
}
У 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;
}
Как всегда — демо на codepen.