До недавнего времени далеко не все свойства CSS можно было анимировать. Например, чтобы создать эффект плавного появления или исчезновения элемента, приходилось использовать свойство opacity, а не display, поскольку display нельзя было анимировать. А проблема в том, что визуально скрытый элемент всё же оставался на странице.

В статье сравниваем традиционные методы и новые функции Chrome, с помощью которых можно анимировать свойство видимости и изменение размера элемента.

Проблема с анимацией display и размера элемента

Скорее всего, вам уже приходилось создавать эффект плавного появления или исчезновения некоторых элементов CSS. Самый распространённый метод — применить анимацию или переход к свойству элемента opacity. Однако если указать нулевую непрозрачность элемента, он не удаляется, а просто становится невидимым. Чаще всего этого вполне достаточно.

Но предположим, у вас есть список дел, из которого пользователи могут удалять пункты. Чтобы создать анимацию выхода, когда пункт списка плавно исчезает, обычно используют opacity. Однако если в списке нужно задать высоту, то для свойства display выставляется значение none. Проблема в том, что визуально элемент исчезает, но всё равно занимает место в DOM. Это влияет на лэйаут и взаимодействие с пользователем.

Вот пошаговое сравнение двух подходов: в одном используется opacity, во втором — комбинация opacity и display. Пример ниже показывает различия:

Обратите внимание, как сдвигается лэйаут при сочетании display и opacity. Если использовать только opacity, в списке остаются пустые места. Второй метод — opacity + display — решает проблему с лэйаутом. Но при этом эффект получается не особенно плавным, поскольку мы применяем display: none до окончания эффекта. Получается, что элемент исчезает внезапно, а не постепенно.

Свойство opacity может плавно переходить от 0 до 1. А анимировать свойство display невозможно, так как у него нет цифрового диапазона — есть бинарные состояния none, block или другие значения. В CSS нельзя анимировать display, потому что у этого свойства нет промежуточных значений.

Аналогичная проблема возникает у разработчиков, когда они пытаются анимировать естественный размер элемента, например height: auto. Он часто используется для переходов в сворачиваемых секциях, таких как аккордеоны, где в закрытом состоянии высота равна 0px, а в открытом — увеличивается до размера содержимого. Обычно свойства размера, например height, можно анимировать, потому что у них есть начальные и конечные числовые значения. Но анимация с применением auto приводит к проблемам. Браузер не может рассчитать шаги от 0px до auto — приходится использовать обходные пути.

Традиционные приёмы анимации для display и размера элемента

Трудности с анимацией элементов display и размеров элемента преодолевают несколькими способами. Разбираем самые популярные решения на базе CSS и JavaScript.

Решения на базе CSS

Самый надёжный вариант — использовать opacity вместе со свойством размера, например height или width. В таком случае свойство размера используется, чтобы эффективно удалить элемент из DOM. Для этого подходит свойство transition-delay. По сути, мы добавляем к изменению размера задержку, которая по времени соответствует изменению непрозрачности. Когда элемент плавно исчезает, его размер сразу же становится равен нулю. При этом он удаляется из лэйаута так же эффективно, как если бы мы применили display: none.

В нашем примере со списком дел реализация будет выглядеть примерно так:

li {
  height: 50px; /* any measurable value, not "auto" */
  opacity: 1;
  transition: height 0ms 0ms, opacity 400ms 0ms;
}
.fade-out {
  overflow: hidden; /* Hide the element content, while height = 0 */
  height: 0;
  opacity: 0;
  padding: 0;
  transition: height 0ms 400ms, padding 0ms 400ms, opacity 400ms 0ms;
}

Весь фокус в том, чтобы после задержки указать для свойств height и padding значение 0, как только непрозрачность упадёт до 0. В этом случае время задержки и непрозрачности должно совпадать, — в нашем случае они равны 400 мс. За счёт height: 0 элемент списка не взаимодействует с лэйаутом. Как мы уже обсуждали ранее, height: auto динамически подстраивается под содержимое, поэтому его нельзя анимировать. Чтобы анимация работала корректно, нужно установить для элемента конкретную фиксированную высоту.

Другой распространённый приём — сделать видимость скрытой. Но при этом элемент не удаляется из DOM: он продолжает работать на лэйауте как обычный элемент, то есть влияет на расположение окружающих элементов.

В CSS наиболее распространённое решение для анимации элемента от или до его естественного размера (или height: auto) — использовать max-height вместо height. Подход не самый элегантный, но поставленную задачу решает. По сути, вы задаёте для max-height значение, большее, чем может быть у элемента. Таким образом удаётся имитировать плавный переход, похожий на анимацию элемента с фиксированной высотой:

.collapsible {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s ease;
}

.collapsible.open {
  max-height: 500px;
} 

Очевидный минус этого подхода в том, что max-height всегда должна быть больше фактического размера содержимого внутри элемента. Время перехода также будет неточным, если высота содержимого не совпадает со значением max-height.

Допустим, высота содержимого 400 px, а вы задаёте 1000 px для max-height. Технически анимация продолжается весь заданный период (скажем, две секунды). Но визуально элемент перестанет расти, как только достигнет фактической высоты содержимого (400 px), а max-height будет увеличиваться до 1000 px. В этом случае длительность перехода будет меньше указанной.

Решения на базе JavaScript

Рассмотренные решения на базе CSS довольно сложные и могут привести к непредсказуемым результатам. Для решения проблем с CSS-анимацией часто используют JavaScript.

Чтобы после изменения непрозрачности применить значение none для свойства display, можно использовать функцию setInterval или setTimeout. Таким образом мы добавляем задержку, которая по длительности соответствует изменению непрозрачности. После этой задержки можно задать для display значение none. Вот пример:

document.getElementById("fadeButton").addEventListener("click", function () {
  const element = document.getElementById("myElement");
  element.style.opacity = "0";
  setTimeout(() => {
    element.style.display = "none";
  }, 1000); // Match this value with the duration in CSS
});

В приведённом коде после нажатия кнопки элемент исчезает за секунду, а сразу после этого для его свойства display активируется значение none, — по сути, это удаляет его из лэйаута.

Аналогичным образом, чтобы анимировать естественный размер, можно рассчитать высоту элемента в JavaScript и использовать это значение для высоты как конечную точку. Это значительно более точный и надёжный подход. Но не забывайте, что мы всё-таки анимируем свойство height.

Очевидное преимущество здесь в том, что вы динамически задаёте высоту, исходя из фактического содержимого элемента. Переход совпадает с настоящей высотой — вам не нужно угадывать max-height.

Вот как это можно сделать:

document.getElementById("toggleButton").addEventListener("click", function () {
  const el = document.querySelector(".expandable");
  const contentHeight = el.scrollHeight;
  el.style.height = contentHeight + "px";
  el.addEventListener("transitionend", function (e) {
    el.removeEventListener("transitionend", arguments.callee);
    el.style.height = "auto";
  });
});

В этом примере мы расширяем секцию, которая начинается с высоты 0. С помощью scrollHeight мы захватываем полную высоту содержимого, затем используем её как конечную точку для перехода. После завершения перехода переключаем настройки высоты на auto. Теперь браузер автоматически корректирует высоту контейнера в зависимости от содержимого. Это необязательное действие, но оно будет кстати, если со временем содержимое в контейнере будет меняться.

Функции CSS для анимирования естественного размера элемента и свойств display

Теперь рассмотрим инструменты, которые позволяют написать более ясный и короткий код для упрощения CSS-анимации без применения JavaScript.

Display и ключевые кадры

Правило @keyframes позволяет создавать анимации, контролируя промежуточные шаги анимации. Оно анимирует свойства display и content-visibility в таймлайне ключевых кадров.

Мы не комбинируем none и block у display: это в принципе невозможно. Вместо этого мы ждём, пока завершатся все остальные эффекты, и потом переключаем состояние display. Примерно то же самое мы делали с JavaScript: дожидались завершения перехода, а потом применяли display: none.

В Chrome Dev Blog есть классное демо, из которого становится понятно, что к чему:

Сначала для непрозрачности задаётся значение 0 на период более 250 мс. Как только эта последовательность действий завершается, для свойства display сразу же включается значение none:

.fade-out {
  animation: fade-out 0.25s forwards;
}

/* Keyframe animations */
@keyframes fade-out {
  100% {
    opacity: 0;
    display: none;
  }
}

Самый большой плюс здесь в том, что можно относительно легко создавать сложные анимации с применением свойства display, которые трудно реализовать с помощью CSS или JavaScript.

Как transition-behavior упрощает переходы display

Эффект плавного исчезновения тоже можно создать с помощью перехода, используя свойство transition-behavior. Оно позволяет применять переходы к свойствам, для которых характерна дискретная анимация, например display. Используя allow-discrete, можно анимировать свойство display. Вот простенький пример:

.card {
  transition: opacity 0.5s, display 0.5s;
  transition-behavior: allow-discrete; /* this is essential */
}

.card.fade-out {
  opacity: 0;
  display: none;
}

Анимации входа с @starting-style

Чаще всего сложные анимации входа создают только с помощью JavaScript. Правило @starting-style значительно упрощает эту задачу.

Если применить стиль к элементу, браузер сможет найти его до того, как элемент отобразится на странице. Здесь можно задать исходное состояние анимации входа. После того как элемент отрендерится, он возвращается в своё состояние по умолчанию, «до открытия».

Вот базовый пример:

.card {
  @starting-style {
    opacity: 0;
  }

  opacity: 1;
  transition: opacity 0.5s;
}

Карточка плавно появляется после загрузки DOM. @starting-style можно использовать для всех видов анимаций входа:

Анимация естественных размеров с помощью calc-size()

Функция calc-size позволяет выполнять безопасные и надёжные вычисления с внутренними значениями. Она поддерживает работу с четырьмя ключевыми словами: auto, min-content, max-content и fit-content. Это особенно полезно для анимации элементов до и от их естественного размера (размера, который определяется содержимым элемента).

calc-size позволяет выполнять анимацию для любой высоты, которую можно указать в CSS до нуля или до небольшого фиксированного значения. Вот простой пример разворачивания сворачиваемой секции с height: 0 до auto:

.card { 
  height: 0; 
}

.card.open { 
  height: calc-size(auto);
}

Совместимость с браузерами

Большинство этих функций предназначены для современных анимаций и не являются существенными компонентами DOM. Однако нелишним будет проверить их совместимость с браузерами:

  • Свойство display поддерживает анимацию @keyframe в Chrome 116+ и Opera 102+. Поддержка этих функций в Firefox и Safari в процессе разработки.

  • Свойство transition-behavior вышло в Chrome версии 117. Оно совместимо со всеми известными браузерами, кроме Firefox, где поддержка должна стать доступной в скором времени.

  • Правило @starting-style появилось в Chrome 117. Оно поддерживается всеми основными браузерами, кроме Firefox, в котором ещё не реализована анимация с display: none.

  • calc-size() — последняя функция, которая появилась в Chrome 129, так что на данный момент её поддержка реализована только в Chrome и Edge. Но остальные браузеры скоро подтянутся.

Резюмируем

Мы выяснили, с какими трудностями сталкиваются разработчики при анимировании свойств CSS display и размеров элементов. Чтобы анимировать свойства, которые нельзя анимировать напрямую, можно применять традиционные, но довольно корявые решения на базе CSS и JavaScript.

Анимация display с помощью ключевых кадров, функция calc-size() и свойство transition-behavior заметно упрощают реализацию анимаций. С ними создавать простые анимации можно без использования JavaScript.


Чтобы расти, нужно выйти из привычной зоны и сделать шаг к переменам. Можно изучить новое, начав с бесплатных занятий:

Или открыть перспективы и получить повышение с профессиональным обучением и переподготовкой:

Комментарии (1)


  1. artptr86
    22.11.2024 12:51

    calc-size() — последняя функция, которая появилась в Chrome 129, так что на данный момент её поддержка реализована только в Chrome и Edge. Но остальные браузеры скоро подтянутся.

    Я бы не был так уверен. В Firefox и Safari задача уже полгода висит и ни на кого не назначена. Так-то и сам стандарт CSS Values and Units Module Level 5 ещё в драфте.