Анимация появления и исчезновения
Переход из урока «Переходы» анимирует смену значения. Но у только что появившегося элемента не было «прошлого» значения — переходить не от чего. А display: none вообще не анимируется: элемент исчезает мгновенно. Современный CSS закрывает оба пробела.
@starting-style: откуда начинать
Правило @starting-style задаёт стили, с которых элемент стартует в момент появления. Браузер берёт их как «прошлое» значение и запускает переход к обычным стилям. Нажмите «Сбросить» над примером, чтобы увидеть ещё раз:
<style>
.note {
padding: 12px;
background: honeydew;
transition: opacity 1s, translate 1s;
}
@starting-style {
.note { opacity: 0; translate: 0 20px; }
}
</style>
<div class="note">Я плавно выехала снизу</div>Без @starting-style блок просто появился бы сразу на месте. Здесь translate — отдельное свойство сдвига, аналог transform: translate().
Переход из display: none
display переключается рывком: значения «наполовину скрыт» не бывает. Такие свойства называют дискретными. Запись allow-discrete (полностью — transition-behavior: allow-discrete) разрешает им участвовать в переходе: при скрытии display сменится на none в самом конце, когда прозрачность уже дошла до нуля:
<style>
.box {
padding: 12px;
margin-top: 8px;
background: lavender;
transition: opacity 0.5s, display 0.5s allow-discrete;
}
.box.hidden { display: none; opacity: 0; }
@starting-style {
.box { opacity: 0; }
}
</style>
<button onclick="document.querySelector('.box').classList.toggle('hidden')">Показать / скрыть</button>
<div class="box">Плавно появляюсь и плавно исчезаю</div>@starting-style отвечает за появление, а allow-discrete — за исчезновение. Нужны оба.
Диалог и popover
Окна <dialog> и блоки popover из урока «Раскрывающиеся блоки и диалоги» тоже скрыты через display: none. Схема та же; кроме display в переход добавляют свойство overlay — оно держит окно поверх страницы, пока идёт анимация закрытия:
<style>
body { min-height: 200px; }
dialog {
opacity: 0;
scale: 0.9;
transition: opacity 0.3s, scale 0.3s, display 0.3s allow-discrete, overlay 0.3s allow-discrete;
}
dialog[open] { opacity: 1; scale: 1; }
@starting-style {
dialog[open] { opacity: 0; scale: 0.9; }
}
</style>
<button onclick="document.querySelector('#hi').showModal()">Открыть окно</button>
<dialog id="hi">
<p>Окно открылось плавно.</p>
<form method="dialog"><button>Закрыть</button></form>
</dialog>Для popover вместо dialog[open] пишут [popover]:popover-open.
Высота auto: interpolate-size
Ещё одна давняя проблема — переход к height: auto. Браузер не умел считать промежуточные значения между числом и словом auto, и блок раскрывался рывком. Строка interpolate-size: allow-keywords у :root разрешает такие переходы на всей странице. Наведите курсор на блок:
<style>
:root { interpolate-size: allow-keywords; }
.more {
height: 24px;
overflow: hidden;
padding: 8px;
background: lightyellow;
transition: height 0.4s;
}
.more:hover { height: auto; }
</style>
<div class="more">Наведите — я раскроюсь.<br>Вторая строка.<br>Третья строка.<br>Четвёртая строка.</div>На момент написания interpolate-size работает в Chrome и Edge; в других браузерах блок просто раскроется сразу, без анимации. Актуальную поддержку проверяйте на caniuse.com.
Поддержка
@starting-style и allow-discrete поддерживаются во всех основных браузерах с 2024 года. Свойство overlay есть не везде: без него закрытие окна может пройти без анимации, но окно всё равно закроется. Все эти приёмы — прогрессивное улучшение: старый браузер покажет и спрячет элемент мгновенно, как раньше.
Частая ошибка
@starting-style перед основным правилом. У селекторов одинаковая специфичность, поэтому побеждает то, что ниже: обычный стиль перекрывает стартовый, и появление идёт без анимации. Нажмите «Сбросить»:
<style>
@starting-style { .bad { opacity: 0; } }
.bad, .good { opacity: 1; padding: 8px; margin: 4px 0; background: mistyrose; transition: opacity 1.5s; }
@starting-style { .good { opacity: 0; } }
</style>
<div class="bad">Стартовый стиль выше — появляюсь сразу</div>
<div class="good">Стартовый стиль ниже — проявляюсь плавно</div>Пишите @starting-style после правила элемента или вкладывайте его прямо внутрь правила.