Плавное изменение высоты блока без jQuery

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

К сожалению, корректного решения данной задачи c помощью CSS нет, так как значение auto для высоты не может быть анимировано с помощью свойства transition

В библиотеке jQuery для этого есть специальные методы slideDown(), slideUp(), slideToggle(), но подключать целую библиотеку не всегда есть смысл или возможность

Сделаем простой вариант решения для этой задачи

Результат можно сразу посмотреть на Codepen или прямо в статье чуть ниже

Верстаем основу

Сначала сделаем структуру HTML по методологии БЭМ

Элемент <div class="item__content"> плавно будет менять высоту, чуть ниже мы зададим ему несколько обязательных CSS-свойств.

Обратите внимание, что сам контент должен находится в дочернем/вложенном блоке, в данном случае в <div class="item__body">

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Smooth Height</title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>
    <section class="section">
      <div class="container">
        <div class="section__item item">
          <div class="item__button">Lorem ipsum dolor sit amet, consectetur adipisicing.</div>
          <div class="item__content"> <!-- этот элемент будет плавно менять высоту -->
            <div class="item__body">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus voluptatum minus ipsum quas, eum ad inventore perferendis maiores distinctio nisi maxime sunt numquam veniam, tempore. Id error deleniti, odio atque.</div>
          </div>
        </div>
        <div class="section__item item">
          <div class="item__button">Lorem ipsum dolor sit amet, consectetur adipisicing.</div>
          <div class="item__content"> <!-- этот элемент будет плавно менять высоту -->
            <div class="item__body">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus voluptatum minus ipsum quas, eum ad inventore perferendis maiores distinctio nisi maxime sunt numquam veniam, tempore. Id error deleniti, odio atque.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus voluptatum minus ipsum quas, eum ad inventore perferendis maiores distinctio nisi maxime sunt numquam veniam, tempore. Id error deleniti, odio atque.</div>
          </div>
        </div>
      </div>
    </section>
    <script src="js/main.js"></script>
  </body>
</html>


Добавим CSS стили

Воспользуемся CSS-переменными для управления цветом

Активное состояние элемента будем определять через data-атрибут data-open.

Если значение data-атрибута data-open будет равно true, селектор активного элемента будет выглядеть следующим образом - .section__item[data-open="true"]

Чтобы элемент .item__content мог плавно менять высоту, ему необходимо задать несколько обязательных CSS-свойств

.item__content {
  max-height: 0; /* изначально блок свёрнут */
  overflow: hidden; /* полностью скрывает дочерний блок */
  transition: max-height 0.9s; /* время анимации можно менять по желанию */
}


Все CSS стили для HTML структуры, описанной выше

:root {
  --main: #6d6de4;
  --main-darken: var(--main);
}
body {
  background: #121212;
  color: #fff;
}
.section {
  padding: 60px 0;
}
.section__item[data-open="true"] {
  --main-darken: #4c4cdd;
}
.section__item:not(:last-child) {
  margin-bottom: 30px;
}
.container {
  max-width: 1140px;
  padding: 0 30px;
  margin: 0 auto;
}
.item {
  overflow: hidden;
  border-radius: 30px;
}
.item__button {
  font-weight: bold;
  padding: 15px 30px;
  display: flex;
  align-items: center;
  min-height: 60px;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  cursor: pointer;
  background: var(--main-darken);
  transition: 0.6s;
}
.item__content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.9s;
}
.item__body {
  padding: 30px;
  background: var(--main);
  transition: 0.6s;
  line-height: 1.8;
  font-size: 16px;
}


Пишем логику на JavaScript

Кратко опишу логику кода.

При клике на кнопку/заголовок элемента - проверяем, активен ли элемент. Если не активен, то делаем его активным, и добавляем inline-свойство max-height для блока контента со значением, равной значению его полной высоты scrollHeight. Так как теперь высота блока с контентом известна, то свойство transition работает в обычном режиме и постепенно увеличивает max-height до установленного значения.

При повторном нажатии на кнопку/заголовок, сбрасываем ранее установленное inline-свойство max-height, и блок с контентом сворачивается к своему начальному нулевому значению, указанном в CSS

При изменении размеров окна, проверяем - если элемент активен и текущее значение max-height отличается от полной высоты блока с контентом, то корректируем значение max-height

Код с комментариями

document.addEventListener('DOMContentLoaded', () => { // Структура страницы загружена

  const smoothHeight = (itemSelector, buttonSelector, contentSelector) => { // объявляем основную функцию, которая принимает в качестве параметров селекторы элемента, кнопки внутри элемента и блока с контентом

    const items = document.querySelectorAll(itemSelector) // находим все элементы по переданному селектору в параметре itemSelector и записываем в константу items

    if (!items.length) return // если таких элементов нет, прекращаем выполнение функции

    items.forEach(el => { // для каждого элемента
      const button = el.querySelector(buttonSelector) // находим кнопку и записываем в константу button
      const content = el.querySelector(contentSelector) // находим блок с контентом и записываем в константу content

      button.addEventListener('click', () => { // при клике на кнопку
        if (el.dataset.open !== 'true') { // если значение data-атрибута open у элемента не равно 'true' и блок с контентом еще не отображен
          el.dataset.open = 'true' // тогда устанавливаем значение 'true'
          content.style.maxHeight = `${content.scrollHeight}px` // и блоку с контентом устанавливаем inline-свойсво max-height со вычисленным значением полной высоты этого блока
        } else { // если блок с контентом отображен и значение data-атрибута open у элемента равно 'true'
          el.dataset.open = 'false' // тогда устанавливаем значение 'false'
          content.style.maxHeight = '' // и сбрасываем ранее установленное inline-свойсво max-height
        }
      })

      const onResize = () => { // объявляем функцию onResize, которая будет корректировать значение inline-свойства max-height при изменении размеров окна браузера
        if (el.dataset.open === 'true') { // если значение data-атрибута open у элемента равно 'true' (коректировать высоту нужно только если блок контента отображен)
          if (parseInt(content.style.maxHeight) !== content.scrollHeight) { // если текущее значение inline-свойства max-height у блока контента не равно полной высоте
            content.style.maxHeight = `${content.scrollHeight}px` // только тогда блоку с контентом корректируем значение inline-свойсва max-height
          }
        }
      }

      window.addEventListener('resize', onResize) // вызываем функцию onResize при изменении размеров окна браузера
    })

  }

  smoothHeight('.section__item', '.item__button', '.item__content') // вызываем основную функцию smoothHeight и передаем в качестве параметров  необходимые селекторы

})


Получаем следующий результат - элементы активны

Lorem ipsum dolor sit amet, consectetur adipisicing.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus voluptatum minus ipsum quas, eum ad inventore perferendis maiores distinctio nisi maxime sunt numquam veniam, tempore. Id error deleniti, odio atque.
Amet, consectetur adipisicing.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus voluptatum minus ipsum quas, eum ad inventore perferendis maiores distinctio nisi maxime sunt numquam veniam, tempore. Id error deleniti, odio atque.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus voluptatum minus ipsum quas, eum ad inventore perferendis maiores distinctio nisi maxime sunt numquam veniam, tempore. Id error deleniti, odio atque.


Итоги

Данный способ не универсальный, но поняв логику, вы можете добалять/изменять функциональность этого примера для конкретной задачи

Буду рад, если статья оказалась полезной

Спасибо за ваше внимание и уделённое время!



Полезные ссылки

Использование data-* атрибутов

learn.javascript.ru - Функции

MDN Web Docs - Шаблонные строки

CSS-переменные: как сделать темную тему