CSS Веерное раскрытие с grid и @property

Источник: «CSS Fan Out with Grid and @property»
Анимация веерное раскрытие подразумевает последовательное раскрытие элементов из стопки с эффектом подпрыгивания. Используя CSS grid, мы избавляемся от лишней работы по позиционированию.

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

Эффект обычно достигается за счёт тайминга и позиционирования каждого элемента по отдельности с очень жёстко заданными значениями. Однако это может оказаться очень трудоёмкой задачей. Можно сделать всё немного проще, если позволить родительскому контейнеру элементов сделать это за нас. Вот результат, полученный таким образом:

See the Pen

В HTML, это группа элементов (плюс пустой элемент — позже я объясню, зачем он нужен), окружённая двумя элементами <input type='radio'> для открытия и закрытия элементов соответственно.

<section class="items-container">
<p class="items"><!--empty--></p>
<label class="items close">
Close the messages<input type="radio" name="radio">
</label>
<p class="items">Alert from Project X</p>
<p class="items">&#x1F429; Willow's appointment at <i>Scrubby's</i></p>
<p class="items">Message from (-_-)</p>
<p class="items">NYT Feed: <u>Weather In... (Read more)</u></p>
<p class="items">6 more items to check in your vacation list!</p>
<label class="items open">
Show the messages<input type="radio" name="radio">
</label>
</section>

Нам нужен контейнер grid, чтобы это работало, поэтому давайте превратим <section>, контейнер с элементами, в такой элемент. Можно использовать список или любой другой элемент, который кажется вам семантически подходящим.

.items-container {
display: grid;
}

Теперь создадим CSS переменную integer со значением, равным количеству элементов внутри контейнера (включая открытые и закрытые элементы управления, а также пустой элемент). Это необходимо для того, чтобы реализовать последовательное раскрытие и скрытие элементов в стилевом правиле контейнера grid.

Также зарегистрируем ещё одну CSS переменную с типом данных length, используемую для анимирования высоты каждого элемента при открытии и закрытии элемента управления, для более плавного выполнения общего действия.

@property --int {
syntax: "<integer>";
inherits: false;
initial-value: 7;
}

@property --hgt {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}

Используем созданные CSS переменные --int и --hgt, чтобы добавить в контейнер grid необходимое количество строк grid с нулевой высотой.

.items-container {
display: grid;
grid-template-rows: repeat(calc(var(--int)), var(--hgt));
}

При прямом добавлении --int к repeat() в Safari получалась пятнистая анимация, поэтому я прогнал её через calc(), и анимация заработала (мы рассмотрим её в ближайшее время). Однако вычисления calc() продолжали пропускать один элемент в итерации из-за того, как вычислялось значение 0. Поэтому пришлось добавить пустой элемент, компенсирующий это исключение.

Если бы Safari не давал клякс, мне бы не понадобился пустой элемент, --int initial-value было бы 6, а значение grid-template-rows было бы просто repeat(var(--int), 0px). На самом деле, при такой настройке я получил хорошие результаты анимации как в Firefox, так и в Chrome.

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

Теперь перейдём к анимации:

@keyframes open { to { --int: 0; --hgt:60px;} }
@keyframes close { to { --int: 6; --hgt:0px;} }
.item-container {
display: grid;
grid-template-rows: repeat(calc(var(--int)), var(--hgt));
&:has(.open :checked) {
/* open action */
animation: open .3s ease-in-out forwards;
.open { display: none; }
}
&:has(.close :checked) {
/* close action */
--int: 0;
--hgt: 60px;
animation: close .3s ease-in-out forwards;
}
}

Когда input находится в состоянии checked, выполняется keyframe-анимация open, а сам элемент управления скрывается с помощью display: none.

Класс open изменяет значение --int с его initial-value, 7, до значения, установленного в правиле @keyframes (0), в течение заданного периода времени (.3s). Этот декремент удаляет нулевую высоту из каждого ряда grid, один за другим, таким образом последовательно раскрывая все элементы за .3s или 300ms. Одновременно значение --hgt увеличивается до 60px с начального значения 0px. Это увеличивает высоту каждого элемента по мере его появления на экране.

Когда input для скрытия всех элементов находится в состоянии checked, выполняется keyframe-анимация close, устанавливающая значение --int в 0 и значение --hgt в 60px.

Класс close изменяет значение --int, которое сейчас равно 0, на значение, объявленное в его правиле: 7. Это увеличение устанавливает нулевую высоту для каждой строки grid, по очереди, таким образом последовательно скрывая все элементы. Одновременно значение --hgt уменьшается до 0px. Это уменьшает высоту каждого элемента по мере его исчезновения с экрана.

Для выполнения действия закрытия, вместо того чтобы создавать уникальную анимацию закрытия, я попробовал использовать анимацию открытия с animation-direction: reverse. К сожалению, результат получился прерывистым. Поэтому я сохранил уникальные анимации для действий открытия и закрытия отдельно.

Кроме того, чтобы отполировать пользовательский интерфейс, я добавил анимацию переходов для промежутков между строками и цвета текста. В промежутках между строками установлена функция синхронизации анимации cubic-bezier() для создания слабого пружинящего эффекта.

.scroll-container {
display: grid;
grid-template-rows: repeat(calc(var(--int)), 0px); /* serves the open and close actions */
transition: row-gap .3s .1s cubic-bezier(.8, .5, .2, 1.4);
&:has(.open :checked) {
/* open action */
animation: open .3s ease-in-out forwards;
.open { display: none; }
/* styling */
row-gap: 10px;
.items { color: rgb(113 124 158); transition: color .3s .1s;}
.close { color: black }
}
&:has(.close :checked) {
/* close action */
--int: 0;
animation: close .3s ease-in-out forwards;
/* styling */
row-gap: 0;
.items { color: transparent; transition: color .2s;}
}
}

При расширении строки gap увеличивается до 10px, а цвет текста становится прозрачным. При сжатии строки gap уменьшается до 0, а цвет текста становится прозрачным. На этом пример завершён! Вот Pen ещё раз:

See the Pen

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

Комментарии


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

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

Новое в Symfony 7.2: Улучшения ограничений

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

Испортили git rebase?