Статья, в которой рассмотрим процесс создания вертикального аккордеона для сайта с использованием 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>

Многоуровневое вертикальное меню аккордеон

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

Многоуровневое вертикальное меню аккордеон для сайта

HTML, CSS и JavaScript код многоуровневого меню:

<style>
  .accordion {
    padding-left: 0;
  }

  .accordion-item {
    margin-bottom: 2px;
  }
  .accordion-item:last-child {
    margin-bottom: 0;
  }

  .accordion-item-header {
    padding: 10px 15px 10px 35px;
    background: #2e7d32;
    color: #fff;
    cursor: pointer;
    position: relative;
  }

  .accordion-item-header::before {
    display: inline-block;
    content: "";
    position: absolute;
    border-top: 8px solid currentColor;
    border-right: 8px solid transparent;
    border-bottom: 0;
    border-left: 8px solid transparent;
    top: 50%;
    margin-top: -4px;
    left: 12px;
  }

  .accordion-item-header::after {
    display: inline-block;
    content: "";
    position: absolute;
    border-top: 8px solid #2e7d32;
    border-right: 8px solid transparent;
    border-bottom: 0;
    border-left: 8px solid transparent;
    top: 50%;
    margin-top: -6px;
    left: 12px;
  }

  .accordion-item-content {
    background: #dcedc8;
    color: #1b5e20;
    display: none;
    list-style-type: none;
  }

  .accordion-item.show > .accordion-item-content {
    padding: 10px 15px;
    display: block;
  }

  .accordion-item.show > ul.accordion-item-content {
    padding-top: 0;
    padding-bottom: 0;
    padding-right: 0;
    background: #fff;
  }

  .accordion-item.show > .accordion-item-header {
    background: #4caf50;
    color: #fff;
    margin-bottom: 2px;
  }

  .accordion-item.show > .accordion-item-header::before {
    transform: rotate(-90deg);
  }

  .accordion-item.show > .accordion-item-header::after {
    transform: rotate(-90deg);
    margin-top: -4px;
    left: 10px;
    border-top-color: #4caf50;
  }

  .accordion-item-header:focus,
  .accordion-item-header:hover,
  .accordion-item.show > .accordion-item-header:focus,
  .accordion-item.show > .accordion-item-header:hover {
    background: #1B5E20;      
  }

  .accordion-item-header:focus::after,
  .accordion-item-header:hover::after,
  .accordion-item.show > .accordion-item-header:focus::before,
  .accordion-item.show > .accordion-item-header:hover::after {
    border-top-color: #1B5E20;
  }
</style>

...
<ul id="accordion" class="accordion" style="max-width: 400px; margin: 0 auto;">
  <li class="accordion-item show">
    <div class="accordion-item-header">
      Заголовок 1
    </div>
    <ul class="accordion-item-content">
      <li class="accordion-item">
        <div class="accordion-item-header">
          Заголовок 1.1
        </div>
        <div class="accordion-item-content" style="height: 200px;">
          Контент 1.1...
        </div>
      </li>
      <li class="accordion-item">
        <div class="accordion-item-header">
          Заголовок 1.2
        </div>
        <div class="accordion-item-content" style="height: 200px;">
          Контент 1.2...
        </div>
      </li>
      <li class="accordion-item">
        <div class="accordion-item-header">
          Заголовок 1.3
        </div>
        <div class="accordion-item-content" style="height: 200px;">
          Контент 1.3...
        </div>
      </li>
    </ul>
  </li>
  <li class="accordion-item">
    <div class="accordion-item-header">
      Заголовок 2
    </div>
    <div class="accordion-item-content" style="height: 200px;">
      Контент 2...
    </div>
  </li>
  <li class="accordion-item">
    <div class="accordion-item-header">
      Заголовок 3
    </div>
    <div class="accordion-item-content" style="height: 200px;">
      Контент 3...
    </div>
  </li>
  <li class="accordion-item">
    <div class="accordion-item-header">
      Заголовок 4
    </div>
    <ul class="accordion-item-content">
      <li class="accordion-item">
        <div class="accordion-item-header">
          Заголовок 4.1
        </div>
        <div class="accordion-item-content" style="height: 200px;">
          Контент 4.1...
        </div>
      </li>
      <li class="accordion-item">
        <div class="accordion-item-header">
          Заголовок 4.2
        </div>
        <div class="accordion-item-content" style="height: 200px;">
          Контент 4.2...
        </div>
      </li>
    </ul>
  </li>
</ul>

<script>
  var accordion = (function (element) {

    var _getActiveItems = function (elements) { // функция для получения элементов с указанным классом
      var items = [];
      elements.forEach(function (item) {
        if (item.classList.contains('show')) {
          items.push(item);
        }
      });
      return items;
    };

    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,
          activeItems = _getActiveItems(_items);

        if (!activeItems.length) { // добавляем класс show к элементу (в зависимости от выбранного заголовка)
          item.classList.add('show');
        } else {
          // удаляем класс show    
          activeItems.forEach(function (activeItem) {
            if (!activeItem.contains(item)) {
              activeItem.classList.remove('show');
            }
          });
          item.classList.toggle('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();
        }
      }

    }
  })();


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

</script>