Анимация элемента dialog
Когда в 2022 году элемент <dialog>
стал широко доступен, я был в восторге. Открыть диалог? Легко. Закрыть диалог? Ещё проще. Вложенные диалоги и взаимодействие с клавиатурой? Встроенные, и совершенно бесплатно. Это как жить в будущем.
А как насчёт анимации? Это немного сложнее. На первый взгляд, кажется, что в CSS нет возможности анимировать диалоги — переходы и анимация не работают. JavaScript может сделать это, но это потребует управления состоянием диалогов вручную, что в первую очередь потеряет простоту использования <dialog>
.
К счастью, благодаря современному CSS это можно сделать, не прибегая к JavaScript.
В этой статье мы рассмотрим анимацию открытия и закрытия отдельно, обсудим решения с использованием переходов и анимации для каждого из них.
Чтобы не усложнять код, я ограничусь только анимацией opacity
, хотя эти приёмы применимы и к более сложным примерам.
Анимация открытия
Переход с @starting-style
Возможно, вы уже пробовали что-то подобное, но обнаружили, что это не работает:
dialog {
transition: opacity 1s;
opacity: 0;
&[open] {
opacity: 1;
}
}
Проблема в том, что когда открывается <dialog>
, браузер не знает, от какого значения opacity
он должен перейти. Первое обновление стиля, которое получает <dialog open>
, устанавливает значение opacity: 1
, и поскольку это также конечное значение, перехода/transition не происходит. Мы видим, что эта проблема возникает при каждой попытке перехода любого элемента, который изменяется на display: none
или из него. Как решить эту проблему?
Один из способов — @starting-style
, at-правило, позволяющее указать значения, от которых необходимо переходить при первом рендеринге элемента.
Его можно вложить прямо в существующее правило [open]
, как показано ниже:
dialog {
transition: opacity 1s;
opacity: 0;
&[open] {
opacity: 1;
@starting-style {
opacity: 0;
}
}
}
Успешно! Это все, что нужно, теперь <dialog>
будет менять opacity
при открытии.
Можно считать, что @starting-style
— это третье состояние диалога, состояние "перед открытием". Часто требуется, чтобы оно было таким же, как и состояние "закрыто", и хотя это может показаться раздражающим дублированием, полезно, что его можно определить отдельно, так как это позволяет сделать переходы между открытием и закрытием различными.
Недостатком, по крайней мере на момент написания статьи, является поддержка браузерами. @starting-style
нет в Firefox, и только в последних версиях Chromium и браузерах на базе WebKit. В зависимости от ваших требований, этого может быть вполне достаточно:
- Использование
@starting-style
— это прогрессивное улучшение. В не поддерживающих его браузерах диалог будет просто открываться без перехода. @starting-style
— цель Interop 2024, так что можно ожидать кроссбраузерную поддержку к концу года.
Что же делать, если кроссбраузерная анимация открытия нужна прямо сейчас? Неужели нам не повезло? К счастью, нет.
Анимация с @keyframes
Используя @keyframes
, можно добиться того же эффекта при поддержке браузером только самого <dialog>
и избавиться от необходимости использовать @starting-style
:
dialog[open] {
animation: open 1s forwards;
}
@keyframes open {
from { opacity: 0 }
to { opacity: 1 }
}
Это всё, что нужно! Проблема того, что браузеру нужно знать, какое начальное значение использовать, решается явным объявлением его в анимации.
У @keyframes
есть несколько недостатков, главным из которых требование уникального имени. Это не кажется большой проблемой, но присваивать имена бывает сложно, а конфликты имён могут запутать при отладке. При прочих равных условиях техника, требующая уникального имени, хуже той, которая его не требует.
Однако я предпочитаю использовать именно этот метод, пока @starting-style
не получит практически повсеместную поддержку. На мой взгляд, это не менее читабельно, редко более многословно, и тот факт, что это работает везде, делает меня (и моих клиентов) счастливыми.
Анимация закрытия
К сожалению, когда <dialog>
закрывается, мы сталкиваемся с ещё несколькими проблемами:
- Он меняется на
display: none
. - Он удаляется из верхнего слоя.
Обе эти вещи происходят сразу после срабатывания события close
, и поскольку они обе скрывают элемент, любые анимации или переходы, которые попытаемся выполнить, не будут видны. Нужно отложить их на время завершения анимации, и это можно сделать в одной строке CSS:
transition:
display 1s allow-discrete,
overlay 1s allow-discrete;
В этой декларации есть несколько новых моментов, поэтому расскажем о каждом из них подробнее.
transition-behavior: allow-discrete
Обычно при попытке перехода между дискретными свойствами мы видим, что это не работает, или, точнее, значение свойства обновляется на 0%
, вызывая мгновенное изменение без перехода.
Обычно transition-behavior: allow-discrete
позволяет запросить, чтобы это изменение происходило на 50%
пути перехода, а не на 0%
. Я говорю обычно
, потому что для переходов, включающих display: none
, это изменение будет происходить либо на 100%
, либо на 0%
, в зависимости от того, анимируется ли переход к display: none
или от него. Это гарантирует, что элемент будет оставаться видимым в течение всего времени перехода. Проблема №1 решена.
В настоящее время transition-behavior
недоступен в Firefox и Safari, но поскольку он также является целью Interop 2024 наряду с @starting-style
, оптимистично полагаем, что он будет повсеместно доступен к концу года.
Он также не доступен в неамериканском написание, так что убедитесь, что вы опустили 'u' (примечание, имеется ввиду слово в британском английском — behaviour).
Свойство overlay
Свойство overlay
имеет два возможных значения: auto
и none
, и определяет, должен ли элемент в верхнем слое отображаться в верхнем слое. Проще говоря, элемент с параметром overlay: auto
будет отображаться в верхнем слое и будет виден, а элемент с параметром overlay: none
— нет.
Всё немного усложняется тем, что свойство overlay
довольно уникально, поскольку его нельзя задать самостоятельно. Его нельзя установить непосредственно на элемент или использовать в анимации @keyframes
. Единственный, кто может изменить значение этого свойства, — браузер. Использование его в переходе в сочетании с allow-discrete
— фактически единственный способ взаимодействия с ним.
Это ещё одно свойство, которое переходит иначе, чем обычные дискретные свойства, где оно будет оставаться в режиме overlay: auto
в течение всего перехода. Именно то, что нужно для решения проблемы №2.
Ключевое слово overlay
— единственный метод удержания элемента в верхнем слое, поэтому любое CSS-решение для анимации закрытия <dialog>
будет требовать его. К сожалению, на момент написания статьи оно доступно только в Chromium, и поскольку оно не входит в список целей Interop 2024, возможно, придётся ещё немного подождать с поддержкой кроссбраузерности.
Завершение перехода
Давайте объединим это с предыдущим примером использующим @starting-style
, добавив его к существующему объявлению перехода/transition
:
dialog {
transition:
display 1s allow-discrete,
overlay 1s allow-discrete,
opacity 1s;
opacity: 0;
&[open] {
opacity: 1;
@starting-style {
opacity: 0;
}
}
}
И вот у нас есть <dialog>
с переходом на открытия и закрытия! Если вы ищете самое простое решение, то можете остановиться на нём — проще не бывает.
Анимация закрытия с @keyframes
Если вы, как и я, хотите воспользоваться преимуществами CSS-анимации для создания кроссбраузерной анимации закрытия, придётся сделать немного больше.
Можно использовать только код transition
для обработки анимации закрытия, сохраняя @keyframes
для анимации открытия. Но если вы похожи на меня, вам будет проще понять, если обе анимации будут управляться через @keyframes
.
Поскольку параметры display
и overlay
задаются браузером, нам все равно нужно перевести эти значения за пределы анимации:
dialog {
transition:
display 1s allow-discrete,
overlay 1s allow-discrete;
&[open] {
animation: open 1s forwards;
}
}
Хотя кажется немного странным использовать одновременно анимацию и переход, мне нравится, что код анимации отделен от управления поведением браузера по умолчанию.
Далее следует сама анимация закрытия.
Первым побуждением было использовать ту же анимацию, но воспроизвести её в обратном направлении. К сожалению, это невозможно сделать, поскольку нельзя изменить animation-direction
, не запустив при этом новую анимацию с другим именем.
Вместо этого давайте определим новый набор @keyframes
для анимации закрытия и применим его к состоянию по умолчанию (закрытому):
dialog {
transition:
display 1s allow-discrete,
overlay 1s allow-discrete;
animation: close 1s forwards;
&[open] {
animation: open 1s forwards;
}
}
@keyframes open {
from { opacity: 0 }
to { opacity: 1 }
}
@keyframes close {
from { opacity: 1 }
to { opacity: 0 }
}
И это всё, что нужно! Диалог <dialog>
с кроссбраузерной анимацией открытия и прогрессивно улучшенной анимацией закрытия. Он немного менее лаконичен и содержит больше дубликатов, чем пример с transition
, но вы можете решить, стоит ли дополнительная поддержка браузера того.
Заключение
Честно говоря, удивительно, как мало CSS требуется для того, чтобы это сделать. Такие инструменты, как <dialog>
, overlay
и transition-behavior
, сделали то, что раньше было невероятно сложной задачей, и свели её всего к нескольким строчкам CSS.
Диалоги стали проще, чем когда-либо, и пока мы не поддаёмся искушению использовать их слишком часто, для меня это повод для праздника 🎉.
А что насчёт popover
и ::backdrop
Для простоты объяснения я сосредоточился на элементе <dialog>
, но все, что мы только что рассмотрели, применимо и к элементам popover
и ::backdrop
! Они существуют в верхнем слое, их отображение переключается браузером точно так же, как и <dialog>
, поэтому их можно анимировать с помощью тех же приёмов.
Вот Adam Argyle со сниппетом, который также обрабатывает popover
и ::backdrop
, только обратите внимание, что он использует @starting-style
, так что поддержка пока будет ограничена:
steal this dialog and popover snippet #CSS
— Adam Argyle (@argyleink) May 22, 2024
- transitions
- entry/exit
- backdrop included
ready for richer design system integration
try on Codepen https://t.co/I4yAV2tWNO pic.twitter.com/ygY9wS4Liv