До недавнего времени далеко не все свойства 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.
Чтобы расти, нужно выйти из привычной зоны и сделать шаг к переменам. Можно изучить новое, начав с бесплатных занятий:
Soft Skills: как мягко добиваться карьерных целей;
Как стать UX/UI-дизайнером: создаём свой первый сайт;
Или открыть перспективы и получить повышение с профессиональным обучением и переподготовкой:
Нейросети для каждого: как решать рабочие задачи быстрее;
artptr86
Я бы не был так уверен. В Firefox и Safari задача уже полгода висит и ни на кого не назначена. Так-то и сам стандарт CSS Values and Units Module Level 5 ещё в драфте.