Статья, в которой рассмотрим процесс создания вертикального аккордеона для сайта с использованием CSS и JavaScript (без jQuery).

Процесс создания аккордеона

Процесс разработки вертикального аккордеона будет состоять из:

  • создания HTML разметки;
  • описания его внешнего вида с помощью CSS;
  • написания логики на JavaScript.

Дизайн аккордеона (скриншот):

Аккордеон, заголовки которого отделены друг от друга

HTML код аккордеона и его описание

HTML-разметка аккордеона:

<div id="accordion" class="accordion">
  <div class="accordion-item show">
    <div class="accordion-item-header">
      Заголовок 1
    </div>
    <div class="accordion-item-content">
      Контент 1...
    </div>
  </div>
  <div class="accordion-item">
    <div class="accordion-item-header">
      Заголовок 2
    </div>
    <div class="accordion-item-content">
      Контент 2...
    </div>
  </div>
  ...
</div>

Аккордеон (accordion) состоит из элементов (accordion-item). Каждый элемент в свою очередь включает в себя заголовок (accordion-item-header) и содержимое (accordion-item-content).

Состояние элемента (accordion-item) в аккордеоне определяется с помощью класса show. Если данный класс присутствует, то содержимое элемента (accordion-item-content) показывается. В противном случае оно скрыто.

Переключение состояния элемента (accordion-item) осуществляется посредством нажатия на заголовок (accordion-item-header).

CSS код аккордеона

Стили аккордеона:

.accordion-item-header {
  padding: 10px 15px;
  background: #ed5565;
  color: #fff;
  cursor: pointer;
}
.accordion-item-content {
  background: #383838;
  color: #fff;
  display: none;
}
.accordion-item.show .accordion-item-content {
  padding: 10px 15px;
  display: block;
}
.accordion-item.show .accordion-item-header {
  background: #da4453;
  color: #fff;
}

Данный код выполняет стилизацию элементов аккордеона, добавляет к ним необходимые внутренние отступы, цвет текста, цвет фона и др. Но, кроме этого, он ещё определяет видимость элементов аккордеона с классом accordion-item-content, т.е. содержимого.

По умолчанию элементы, имеющие класс accordion-item-content не отображаются (CSS свойство display равно значению none). Включение отображения определённого элемента (accordion-item-content) осуществляется посредством класса show, который необходимо добавить к его родительскому элементу (accordion-item).

JavaScript код аккордеона

Сценарий (логика) аккордеона:

var accordion = (function (element) {
  var _getItem = function (elements, className) { // функция для получения элемента с указанным классом
    var element = undefined;
    elements.forEach(function (item) {
      if (item.classList.contains(className)) {
        element = item;
      }
    });
    return element;
  };
  return function () {
    var _mainElement = {}, // .accordion
      _items = {}, // .accordion-item
      _contents = {}; // .accordion-item-content 
    var _actionClick = function (e) {
      if (!e.target.classList.contains('accordion-item-header')) { // прекращаем выполнение функции если кликнули не по заголовку
        return;
      }
      e.preventDefault(); // отменям стандартное действие
      // получаем необходимые данные
      var header = e.target,
        item = header.parentElement,
        itemActive = _getItem(_items, 'show');
      if (itemActive === undefined) { // добавляем класс show к элементу (в зависимости от выбранного заголовка)
        item.classList.add('show');
      } else {
        // удаляем класс show у ткущего элемента
        itemActive.classList.remove('show');
        // если следующая вкладка не равна активной
        if (itemActive !== item) {
          // добавляем класс show к элементу (в зависимости от выбранного заголовка)
          item.classList.add('show');
        }
      }
    },
    _setupListeners = function () {
      // добавим к элементу аккордиона обработчик события click
      _mainElement.addEventListener('click', _actionClick);
    };

    return {
      init: function (element) {
        _mainElement = (typeof element === 'string' ? document.querySelector(element) : element);
        _items = _mainElement.querySelectorAll('.accordion-item');
        _setupListeners();
      }
    }
  }
})();

Сценарий JavaScript выполняет очень простые действия. Он добавляет обработчик события click для аккордеона. Далее в зависимости от того по какому заголовку кликнули, он добавляет и (или) удаляет класс show у необходимых(ого) элементов(а).

Инициализация элемента как аккордеон выполняется следующим образом:

// инициализируем элемент с id="accordion" как аккордеон
var accordion1 = accordion();
accordion1.init('#accordion');

Аккордеон, заголовки которого отделены друг от друга

Скриншот аккордеона, элементы которого отделены друг от друга с помощью отступа:

Аккордеон, заголовки которого отделены друг от друга

CSS, добавляющий к элементам аккордеона отступы снизу (margin-bottom):

.accordion-item {
  margin-bottom: .25rem;
}
.accordion-item:last-child {
  margin-bottom: 0;
}

Аккордеон с анимацией появления

Скриншот аккордеона, появление содержимого которого сопровождается CSS анимацией:

Аккордеон на JavaScript с анимацией появления

Стили аккордеона, включающие в себя анимацию (для отображения содержимого):

.accordion {
  border-bottom: 1px solid #ddd;
}
.accordion-item {
  border-top: 1px solid #ddd;      
  border-left: 1px solid #ddd;
  border-right: 1px solid #ddd;
}
.accordion-item-header {
  padding: 10px 15px;
  background: #a4b4bf;
  color: #fff;
  cursor: pointer;
}
.accordion-item-content {
  background: #fff;
  transition: opacity .4s ease;
  visibility: hidden;
  height: 0;
  opacity: 0;
}
.accordion-item.show .accordion-item-content {
  padding: 10px 15px;
  visibility: visible;
  height: auto;
  opacity: 1;
}
.accordion-item.show .accordion-item-header {
  background: #3498db;
  color: #fff;
  border-bottom: 1px solid #ddd;
}

Вертикальное меню аккордеон

Пример настройки аккордеона в качестве вертикального меню.

Дополнительно в JavaScript сценарий добавим небольшой фрагмент кода, который будет показывать сколько подпунктов имеет каждый пункт в этом меню.

Вертикальное меню аккордеон для сайта

CSS и JavaScript:

<style>
/* CSS */
.accordion-item {
  margin-bottom: 1px;
}
.accordion-item:last-child {
  margin-bottom: 0;
}
.accordion-item-header {
  padding: 10px 10px 10px 20px;
  background: #4d5159;
  color: rgba(255, 255, 255, .8);
  cursor: pointer;
  text-transform: uppercase;
  font-family: sans-serif;
  position: relative;
}
.accordion-item.show .accordion-item-header,
.accordion-item-header:hover,
.accordion-item-header:focus {
  background: #3b3e44;
}
.accordion-item-header::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 10px;
  background: rgba(255, 255, 255, .5);
}
.accordion-item-content {
  display: none;
}
.accordion-item.show .accordion-item-content {
  display: block;
}
.accordion-subitems {
  list-style-type: none;
  margin: 0;
  padding: 0;
}
.accordion-subitem>a {
  padding: 10px 10px 10px 20px;
  background: #ebeaec;
  color: #333;
  cursor: pointer;
  text-transform: uppercase;
  font-family: sans-serif;
  position: relative;
  margin-top: 1px;
  display: block;
  text-decoration: none;
}
.accordion-subitem>a::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 10px;
  background: rgba(255, 255, 255, .5);
}
.accordion-subitem.active>a {
  background: #d4d3d5;
  pointer-events: none;
}
.accordion-subitem a:hover,
.accordion-subitem a:focus {
  background: #d4d3d5;
}
.accordion-item-header-count {
  position: absolute;
  top: 50%;
  right: 10px;
  width: 24px;
  height: 24px;
  transform: translateY(-50%);
  text-align: center;
  background: rgba(255, 255, 255, .8);
  color: #000;
  line-height: 24px;
  border-radius: 12px;
}
</style>

<!-- HTML -->
<div id="accordion" class="accordion">
  <div class="accordion-item">
    <div class="accordion-item-header">
      Заголовок 1
    </div>
    <div class="accordion-item-content">
      <ul class="accordion-subitems">
        <li class="accordion-subitem">
          <a href="#">Пункт 1.1</a>
        </li>
        <li class="accordion-subitem active">
          <a href="#">Пункт 1.2</a>
        </li>
        <li class="accordion-subitem">
          <a href="#">Пункт 1.3</a>
        </li>
        ...
      </ul>
    </div>
  </div>
  ...
</div>

<script>
// JavaScript
var accordion = (function (element) {
  var _getItem = function (elements, className) { // функция для получения элемента с указанным классом
    var element = undefined;
    elements.forEach(function (item) {
      if (item.classList.contains(className)) {
        element = item;
      }
    });
    return element;
  };
  return function () {
    var _mainElement = {}, // .accordion
      _items = {}, // .accordion-item
      _contents = {}; // .accordion-item-content 
    var _addItemHeaderCount = function () {
      // количество подпунктов в пункте
      var headers = _mainElement.querySelectorAll('.accordion-item-header');
      headers.forEach(function (header) {
        var countElement = document.createElement('div');
        countElement.className = 'accordion-item-header-count';
        countElement.textContent = header.parentElement.querySelectorAll('.accordion-subitem').length;
        header.appendChild(countElement);
      });
    },
      _actionClick = function (e) {
        if (!e.target.classList.contains('accordion-item-header')) { // прекращаем выполнение функции если кликнули не по заголовку
          return;
        }
        e.preventDefault(); // отменям стандартное действие
        // получаем необходимые данные
        var header = e.target,
          item = header.parentElement,
          itemActive = _getItem(_items, 'show');

        if (itemActive === undefined) { // добавляем класс show к элементу (в зависимости от выбранного заголовка)
          item.classList.add('show');
        } else {
          // удаляем класс show у ткущего элемента
          itemActive.classList.remove('show');
          // если следующая вкладка не равна активной
          if (itemActive !== item) {
            // добавляем класс show к элементу (в зависимости от выбранного заголовка)
            item.classList.add('show');
          }
        }
      },
      _setupListeners = function () {
        // добавим к элементу аккордиона обработчик события click
        _mainElement.addEventListener('click', _actionClick);
    };
    return {
      init: function (element) {
        _mainElement = (typeof element === 'string' ? document.querySelector(element) : element);
        _items = _mainElement.querySelectorAll('.accordion-item');
        _addItemHeaderCount();
        _setupListeners();
      }
    }
  }
})();

var accordion1 = accordion();
accordion1.init('#accordion');

</script>