Анимация элемента dialog

Источник: «Animating the Dialog Element»
Казалось бы, можно просто установить в CSS transition от 0 к 1 для opacity элемента dialog, но это не работает. Необходимо изучить @starting-style, а также ключевые слова overlay и allow-discrete.

Когда в 2022 году элемент <dialog> стал широко доступен, я был в восторге. Открыть диалог? Легко. Закрыть диалог? Ещё проще. Вложенные диалоги и взаимодействие с клавиатурой? Встроенные, и совершенно бесплатно. Это как жить в будущем.

А как насчёт анимации? Это немного сложнее. На первый взгляд, кажется, что в CSS нет возможности анимировать диалоги — переходы и анимация не работают. JavaScript может сделать это, но это потребует управления состоянием диалогов вручную, что в первую очередь потеряет простоту использования <dialog>.

К счастью, благодаря современному CSS это можно сделать, не прибегая к JavaScript.

See the Pen

В этой статье мы рассмотрим анимацию открытия и закрытия отдельно, обсудим решения с использованием переходов и анимации для каждого из них.

Чтобы не усложнять код, я ограничусь только анимацией opacity, хотя эти приёмы применимы и к более сложным примерам.

Анимация открытия

Переход с @starting-style

Возможно, вы уже пробовали что-то подобное, но обнаружили, что это не работает:

dialog {
transition: opacity 1s;
opacity: 0;

&[open] {
opacity: 1;
}
}

See the Pen

Проблема в том, что когда открывается <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;
}
}
}

See the Pen

Успешно! Это все, что нужно, теперь <dialog> будет менять opacity при открытии.

Можно считать, что @starting-style — это третье состояние диалога, состояние "перед открытием". Часто требуется, чтобы оно было таким же, как и состояние "закрыто", и хотя это может показаться раздражающим дублированием, полезно, что его можно определить отдельно, так как это позволяет сделать переходы между открытием и закрытием различными.

Недостатком, по крайней мере на момент написания статьи, является поддержка браузерами. @starting-style нет в Firefox, и только в последних версиях Chromium и браузерах на базе WebKit. В зависимости от ваших требований, этого может быть вполне достаточно:

  1. Использование @starting-style — это прогрессивное улучшение. В не поддерживающих его браузерах диалог будет просто открываться без перехода.
  2. @starting-style — цель Interop 2024, так что можно ожидать кроссбраузерную поддержку к концу года.

Что же делать, если кроссбраузерная анимация открытия нужна прямо сейчас? Неужели нам не повезло? К счастью, нет.

Анимация с @keyframes

Используя @keyframes, можно добиться того же эффекта при поддержке браузером только самого <dialog> и избавиться от необходимости использовать @starting-style:

dialog[open] {
animation: open 1s forwards;
}

@keyframes open {
from { opacity: 0 }
to { opacity: 1 }
}

See the Pen

Это всё, что нужно! Проблема того, что браузеру нужно знать, какое начальное значение использовать, решается явным объявлением его в анимации.

У @keyframes есть несколько недостатков, главным из которых требование уникального имени. Это не кажется большой проблемой, но присваивать имена бывает сложно, а конфликты имён могут запутать при отладке. При прочих равных условиях техника, требующая уникального имени, хуже той, которая его не требует.

Однако я предпочитаю использовать именно этот метод, пока @starting-style не получит практически повсеместную поддержку. На мой взгляд, это не менее читабельно, редко более многословно, и тот факт, что это работает везде, делает меня (и моих клиентов) счастливыми.

Анимация закрытия

К сожалению, когда <dialog> закрывается, мы сталкиваемся с ещё несколькими проблемами:

  1. Он меняется на display: none.
  2. Он удаляется из верхнего слоя.

Обе эти вещи происходят сразу после срабатывания события 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;
}
}
}

See the Pen

И вот у нас есть <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 }
}

See the Pen

И это всё, что нужно! Диалог <dialog> с кроссбраузерной анимацией открытия и прогрессивно улучшенной анимацией закрытия. Он немного менее лаконичен и содержит больше дубликатов, чем пример с transition, но вы можете решить, стоит ли дополнительная поддержка браузера того.

Заключение

Честно говоря, удивительно, как мало CSS требуется для того, чтобы это сделать. Такие инструменты, как <dialog>, overlay и transition-behavior, сделали то, что раньше было невероятно сложной задачей, и свели её всего к нескольким строчкам CSS.

Диалоги стали проще, чем когда-либо, и пока мы не поддаёмся искушению использовать их слишком часто, для меня это повод для праздника 🎉.

А что насчёт popover и ::backdrop

Для простоты объяснения я сосредоточился на элементе <dialog>, но все, что мы только что рассмотрели, применимо и к элементам popover и ::backdrop! Они существуют в верхнем слое, их отображение переключается браузером точно так же, как и <dialog>, поэтому их можно анимировать с помощью тех же приёмов.

Вот Adam Argyle со сниппетом, который также обрабатывает popover и ::backdrop, только обратите внимание, что он использует @starting-style, так что поддержка пока будет ограничена:


See the Pen

Дополнительные материалы

Предыдущая Статья

Якорные ссылки и как их сделать потрясающими

Следующая Статья

Явное управление ресурсами в JavaScript: ключевое слово using