Узнайте, как работать с прокруткой временных шкал и просмотром временных шкал для создания анимации на основе прокрутки декларативным способом.
Анимация, управляемая прокруткой
Анимация, управляемая прокруткой, — распространенный шаблон UX в Интернете. Анимация, управляемая прокруткой, связана с позицией прокрутки контейнера прокрутки. Это означает, что при прокрутке вверх или вниз связанная анимация перемещается вперед или назад в ответ на это. Примерами этого являются такие эффекты, как фоновые изображения параллакса или индикаторы чтения, которые перемещаются при прокрутке.
Аналогичный тип анимации, управляемой прокруткой, — это анимация, которая связана с позицией элемента внутри его контейнера прокрутки. С его помощью, например, элементы могут постепенно появляться по мере их появления.
Классический способ добиться такого рода эффектов — реагировать на события прокрутки в основном потоке , что приводит к двум основным проблемам:
- Современные браузеры выполняют прокрутку в отдельном процессе и поэтому доставляют события прокрутки асинхронно.
- Анимации основного потока могут быть заблокированы .
Это делает невозможным или очень сложным создание высокопроизводительной анимации, управляемой прокруткой и синхронизированной с прокруткой.
В Chrome версии 115 появился новый набор API и концепций, которые можно использовать для включения декларативной анимации с прокруткой: прокрутка временных шкал и просмотр временных шкал.
Эти новые концепции интегрируются с существующими API веб-анимации (WAAPI) и CSS Animations API , что позволяет им унаследовать преимущества, которые дают существующие API. Это включает в себя возможность запускать анимацию, управляемую прокруткой, за пределами основного потока. Да, прочтите это правильно: теперь вы можете получить плавную и плавную анимацию, управляемую прокруткой, идущую за пределами основного потока, всего лишь с помощью нескольких строк дополнительного кода. Что не нравится?!
Анимации в сети, небольшой обзор
Анимация в Интернете с помощью CSS
Чтобы создать анимацию в CSS, определите набор ключевых кадров, используя правило @keyframes
. Свяжите его с элементом, используя свойство animation-name
, а также задав animation-duration
, чтобы определить, сколько времени должна длиться анимация. Доступно больше свойств animation-*
animation-easing-function
и animation-fill-mode
это лишь некоторые из них, — которые все можно объединить в сокращенной версии animation
.
Например, вот анимация, которая масштабирует элемент по оси X и одновременно меняет цвет его фона:
@keyframes scale-up {
from {
background-color: red;
transform: scaleX(0);
}
to {
background-color: darkred;
transform: scaleX(1);
}
}
#progressbar {
animation: 2.5s linear forwards scale-up;
}
Анимация в Интернете с помощью JavaScript
В JavaScript API веб-анимации можно использовать для достижения того же самого. Вы можете сделать это, либо создав новые экземпляры Animation
и KeyFrameEffect
, либо используя гораздо более короткий метод Element
animate()
.
document.querySelector('#progressbar').animate(
{
backgroundColor: ['red', 'darkred'],
transform: ['scaleX(0)', 'scaleX(1)'],
},
{
duration: 2500,
fill: 'forwards',
easing: 'linear',
}
);
Этот визуальный результат приведенного выше фрагмента JavaScript идентичен предыдущей версии CSS.
Сроки анимации
По умолчанию анимация, прикрепленная к элементу, выполняется на временной шкале документа . Его исходное время начинается с 0, когда страница загружается, и начинает идти вперед по мере продвижения часов. Это временная шкала анимации по умолчанию, и до сих пор это была единственная временная шкала анимации, к которой у вас был доступ.
Спецификация анимации, управляемой прокруткой, определяет два новых типа временных шкал, которые вы можете использовать:
- Временная шкала прогресса прокрутки : временная шкала, которая связана с положением прокрутки контейнера прокрутки вдоль определенной оси.
- Просмотр временной шкалы прогресса : временная шкала, которая связана с относительным положением определенного элемента в его контейнере прокрутки.
Прокрутка временной шкалы прогресса
Временная шкала прогресса прокрутки — это временная шкала анимации, которая связана с прогрессом в положении прокрутки контейнера прокрутки, также называемого портом прокрутки или скроллером , вдоль определенной оси. Он преобразует позицию в диапазоне прокрутки в процент прогресса.
Начальная позиция прокрутки представляет прогресс 0%, а конечная позиция прокрутки представляет прогресс 100%. На следующей визуализации вы можете видеть, что прогресс увеличивается от 0% до 100% при прокрутке скроллера сверху вниз.
✨ Попробуйте сами
Временную шкалу прогресса прокрутки часто сокращают до просто «Временная шкала прокрутки».
Посмотреть график прогресса
Этот тип временной шкалы связан с относительным прогрессом конкретного элемента в контейнере прокрутки. Как и в случае с временной шкалой прогресса прокрутки, отслеживается смещение прокрутки скроллера. В отличие от временной шкалы прогресса прокрутки, прогресс определяется относительным положением объекта внутри этого скроллера.
Это в некоторой степени сравнимо с тем, как работает IntersectionObserver
, который может отслеживать, насколько элемент виден в скроллере. Если элемент не виден в скроллере, он не пересекается. Если он виден внутри скроллера — даже в самой маленькой его части — он пересекается.
Временная шкала прогресса просмотра начинается с момента, когда объект начинает пересекаться со скроллером, и заканчивается, когда объект перестает пересекать скроллер. На следующей визуализации вы можете видеть, что прогресс начинает отсчитываться от 0%, когда субъект входит в контейнер прокрутки, и достигает 100% в тот самый момент, когда субъект покидает контейнер прокрутки.
✨ Попробуйте сами
Временную шкалу просмотра прогресса часто называют просто «Просмотр временной шкалы». Можно настроить таргетинг на определенные части временной шкалы просмотра в зависимости от размера объекта, но об этом позже.
Практическая работа с временными шкалами прокрутки
Создание анонимной временной шкалы прогресса прокрутки в CSS
Самый простой способ создать временную шкалу прокрутки в CSS — использовать функцию scroll()
. При этом создается анонимная временная шкала прокрутки, которую можно установить в качестве значения для нового свойства animation-timeline
.
Пример:
@keyframes animate-it { … }
.subject {
animation: animate-it linear;
animation-timeline: scroll(root block);
}
Функция scroll()
принимает аргументы <scroller>
и <axis>
.
Допустимые значения аргумента <scroller>
:
-
nearest
: использует контейнер прокрутки ближайшего предка (по умолчанию) . -
root
: использует область просмотра документа в качестве контейнера прокрутки. -
self
: использует сам элемент в качестве контейнера прокрутки.
Допустимые значения аргумента <axis>
:
-
block
: использует меру прогресса вдоль оси блока контейнера прокрутки (по умолчанию) . -
inline
: использует меру прогресса вдоль встроенной оси контейнера прокрутки. -
y
: использует меру прогресса по оси y контейнера прокрутки. -
x
: использует меру прогресса по оси x контейнера прокрутки.
Например, чтобы привязать анимацию к корневому скроллеру на оси блока, значения, которые нужно передать в scroll()
— это root
и block
. В совокупности значение равно scroll(root block)
.
Демо: индикатор прогресса чтения
В этой демонстрации индикатор прогресса чтения закреплен в верхней части области просмотра. По мере прокрутки страницы вниз индикатор выполнения увеличивается до тех пор, пока не займет всю ширину области просмотра при достижении конца документа. Анонимная временная шкала прогресса прокрутки используется для управления анимацией.
✨ Попробуйте сами
Индикатор прогресса чтения располагается вверху страницы в фиксированном положении. Чтобы использовать составную анимацию, анимируется не width
, а масштаб элемента по оси X с помощью transform
.
<body>
<div id="progress"></div>
…
</body>
@keyframes grow-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
#progress {
position: fixed;
left: 0; top: 0;
width: 100%; height: 1em;
background: red;
transform-origin: 0 50%;
animation: grow-progress auto linear;
animation-timeline: scroll();
}
Временная шкала grow-progress
анимации в элементе #progress
установлена на анонимную временную шкалу, созданную с помощью scroll()
. Для scroll()
не передаются аргументы, поэтому она возвращается к значениям по умолчанию.
Скроллер по умолчанию для отслеживания — nearest
, а ось по умолчанию — block
. Это эффективно нацелено на корневой скроллер, поскольку это ближайший к элементу #progress
скроллер, отслеживая при этом направление его блока.
Создание именованной временной шкалы прогресса прокрутки в CSS
Альтернативный способ определить временную шкалу прогресса прокрутки — использовать именованную. Он немного более подробный, но может пригодиться, если вы не нацеливаетесь на родительский или корневой скроллер, или когда на странице используется несколько временных шкал, или когда автоматический поиск не работает. Таким образом, вы можете идентифицировать временную шкалу прогресса прокрутки по имени, которое вы ей даете.
Чтобы создать именованную временную шкалу прогресса прокрутки для элемента, установите для свойства CSS scroll-timeline-name
в контейнере прокрутки идентификатор по вашему вкусу. Значение должно начинаться с --
.
Чтобы настроить ось для отслеживания, также объявите свойство scroll-timeline-axis
. Допустимые значения такие же, как и у аргумента <axis>
функции scroll()
.
Наконец, чтобы связать анимацию с временной шкалой выполнения прокрутки, установите для свойства animation-timeline
элемента, который необходимо анимировать, то же значение, что и идентификатор, используемый для scroll-timeline-name
.
Пример кода:
@keyframes animate-it { … }
.scroller {
scroll-timeline-name: --my-scroller;
scroll-timeline-axis: inline;
}
.scroller .subject {
animation: animate-it linear;
animation-timeline: --my-scroller;
}
При желании вы можете объединить scroll-timeline-name
и scroll-timeline-axis
в сокращении scroll-timeline
. Например:
scroll-timeline: --my-scroller inline;
Демонстрация: индикатор шагов горизонтальной карусели
В этой демонстрации над каждой каруселью изображений отображается индикатор шагов. Если карусель содержит три изображения, полоса индикатора начинается с ширины 33%, что указывает на то, что вы в данный момент просматриваете первое изображение из трех. Когда последнее изображение находится в поле зрения (определяется прокруткой скроллера до конца), индикатор занимает всю ширину скроллера. Именованная временная шкала прогресса прокрутки используется для управления анимацией.
✨ Попробуйте сами
Базовая разметка галереи такова:
<div class="gallery" style="--num-images: 2;">
<div class="gallery__scrollcontainer">
<div class="gallery__progress"></div>
<div class="gallery__entry">…</div>
<div class="gallery__entry">…</div>
</div>
</div>
Элемент .gallery__progress
абсолютно позиционируется внутри элемента-оболочки .gallery
. Его первоначальный размер определяется пользовательским свойством --num-images
.
.gallery {
position: relative;
}
.gallery__progress {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 1em;
transform: scaleX(calc(1 / var(--num-images)));
}
Контейнер .gallery__scrollcontainer
размещает содержащиеся в нем элементы .gallery__entry
по горизонтали и является элементом, который прокручивается. Отслеживая положение прокрутки, .gallery__progress
анимируется. Это делается путем обращения к именованной шкале прогресса прокрутки --gallery__scrollcontainer
.
@keyframes grow-progress {
to { transform: scaleX(1); }
}
.gallery__scrollcontainer {
overflow-x: scroll;
scroll-timeline: --gallery__scrollcontainer inline;
}
.gallery__progress {
animation: auto grow-progress linear forwards;
animation-timeline: --gallery__scrollcontainer;
}
Создание временной шкалы прогресса прокрутки с помощью JavaScript
Чтобы создать временную шкалу прокрутки в JavaScript, создайте новый экземпляр класса ScrollTimeline
. Передайте пакет свойств с source
и axis
, которую вы хотите отслеживать.
-
source
: ссылка на элемент, скроллер которого вы хотите отслеживать. Используйтеdocument.documentElement
для нацеливания на корневой скроллер. -
axis
: определяет, какую ось отслеживать. Как и в варианте CSS, допустимыми значениями являютсяblock
,inline
,x
иy
.
const tl = new ScrollTimeline({
source: document.documentElement,
});
Чтобы прикрепить его к веб-анимации, передайте его как свойство timeline
и опустите duration
, если таковая имеется.
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
});
Демо: индикатор прогресса чтения, еще раз
Чтобы воссоздать индикатор прогресса чтения с помощью JavaScript, используя ту же разметку, используйте следующий код JavaScript:
const $progressbar = document.querySelector('#progress');
$progressbar.style.transformOrigin = '0% 50%';
$progressbar.animate(
{
transform: ['scaleX(0)', 'scaleX(1)'],
},
{
fill: 'forwards',
timeline: new ScrollTimeline({
source: document.documentElement,
}),
}
);
Визуальный результат идентичен в версии CSS: созданная timeline
отслеживает корневой скроллер и масштабирует #progress
по оси X от 0% до 100% по мере прокрутки страницы.
✨ Попробуйте сами
Практический опыт с просмотром временной шкалы прогресса
Создание временной шкалы анонимного просмотра в CSS
Чтобы создать временную шкалу просмотра прогресса, используйте функцию view()
. Его приемлемыми аргументами являются <axis>
и <view-timeline-inset>
.
-
<axis>
аналогична временной шкале прогресса прокрутки и определяет, какую ось отслеживать. Значение по умолчанию —block
. - С помощью
<view-timeline-inset>
вы можете указать смещение (положительное или отрицательное), чтобы настроить границы, когда элемент считается видимым или нет. Значение должно быть в процентах илиauto
, гдеauto
— значение по умолчанию.
Например, чтобы привязать анимацию к элементу, пересекающему его скроллер на оси блока, используйте view(block)
. Как и в случае с scroll()
, установите это значение для свойства animation-timeline
и не забудьте установить animation-duration
значение auto
.
Используя следующий код, каждое img
будет постепенно появляться при пересечении области просмотра во время прокрутки.
@keyframes reveal {
from { opacity: 0; }
to { opacity: 1; }
}
img {
animation: reveal linear;
animation-timeline: view();
}
Интермеццо: просмотр диапазонов временной шкалы
По умолчанию анимация, связанная с временной шкалой просмотра, прикрепляется ко всему диапазону временной шкалы. Это начинается с момента, когда субъект собирается войти в область прокрутки, и заканчивается, когда субъект полностью покидает область прокрутки.
Также можно связать его с определенной частью временной шкалы просмотра, указав диапазон, к которому он должен быть прикреплен. Это может быть, например, только тогда, когда субъект входит в скроллер. В следующей визуализации прогресс начинает отсчет от 0%, когда объект входит в контейнер прокрутки, но уже достигает 100% с момента его полного пересечения.
Возможные диапазоны просмотра временной шкалы, на которые вы можете настроить таргетинг:
-
cover
: представляет полный диапазон временной шкалы просмотра. -
entry
: представляет диапазон, в течение которого основное поле входит в диапазон видимости прогресса просмотра. -
exit
: представляет диапазон, в течение которого основной блок выходит из диапазона видимости прогресса просмотра. -
entry-crossing
: представляет диапазон, в течение которого основной блок пересекает край конечной границы. -
exit-crossing
: представляет диапазон, в течение которого основной блок пересекает край начальной границы. -
contain
: представляет диапазон, в течение которого основной блок либо полностью содержится, либо полностью покрывает диапазон видимости прогресса просмотра в области прокрутки. Это зависит от того, выше или короче объект скроллера.
Чтобы определить диапазон, необходимо установить начало и конец диапазона. Каждый состоит из имени диапазона (см. список выше) и смещения диапазона для определения позиции внутри этого имени диапазона. Смещение диапазона обычно представляет собой процентное значение от 0%
до 100%
, но вы также можете указать фиксированную длину, например 20em
.
Например, если вы хотите запускать анимацию с момента входа субъекта, выберите entry 0%
в качестве начала диапазона. Чтобы завершить его к моменту входа субъекта, выберите entry 100%
в качестве значения конца диапазона.
В CSS это устанавливается с помощью свойства animation-range
. Пример:
animation-range: entry 0% entry 100%;
В JavaScript используйте свойства rangeStart
и rangeEnd
.
$el.animate(
keyframes,
{
timeline: tl,
rangeStart: 'entry 0%',
rangeEnd: 'entry 100%',
}
);
Используйте встроенный ниже инструмент, чтобы увидеть, что представляет собой каждое имя диапазона и как проценты влияют на начальную и конечную позиции. Попробуйте установить начало диапазона на entry 0%
и конец диапазона на cover 50%
, а затем перетащите полосу прокрутки, чтобы увидеть результат анимации.
Посмотреть запись
Как вы могли заметить, играя с инструментами просмотра диапазонов временной шкалы, некоторые диапазоны могут быть нацелены на две разные комбинации имени диапазона + смещения диапазона. Например, entry 0%
, entry-crossing 0%
и cover 0%
нацелены на одну и ту же область.
Если начало и конец диапазона нацелены на одно и то же имя диапазона и охватывают весь диапазон — от 0% до 100% — вы можете сократить значение до простого имени диапазона. Например, animation-range: entry 0% entry 100%;
можно переписать на гораздо более короткий animation-range: entry
.
Демо: раскрытие изображения
В этой демонстрации изображения исчезают по мере их входа в область прокрутки. Это делается с помощью временной шкалы анонимного просмотра. Диапазон анимации был изменен таким образом, чтобы каждое изображение было полностью непрозрачным, когда оно находится на полпути прокрутки.
✨ Попробуйте сами
Эффект расширения достигается за счет использования анимированного контура клипа. CSS, используемый для этого эффекта, следующий:
@keyframes reveal {
from { opacity: 0; clip-path: inset(0% 60% 0% 50%); }
to { opacity: 1; clip-path: inset(0% 0% 0% 0%); }
}
.revealing-image {
animation: auto linear reveal both;
animation-timeline: view();
animation-range: entry 25% cover 50%;
}
Создание именованной временной шкалы прогресса просмотра в CSS
Подобно тому, как прокрутка временных шкал дает имена версиям, вы также можете создавать именованные временные шкалы просмотра. Вместо свойств scroll-timeline-*
вы используете варианты, которые содержат префикс view-timeline-
, а именно view-timeline-name
и view-timeline-axis
.
Применяются те же типы значений и те же правила поиска по именованной временной шкале.
Демо: раскрытие изображения, повторное посещение
Перерабатывая демонстрационное изображение, представленное ранее, исправленный код выглядит следующим образом:
.revealing-image {
view-timeline-name: --revealing-image;
view-timeline-axis: block;
animation: auto linear reveal both;
animation-timeline: --revealing-image;
animation-range: entry 25% cover 50%;
}
Используя view-timeline-name: revealing-image
, элемент будет отслеживаться в пределах ближайшего к нему скроллера. Это же значение затем используется в качестве значения свойства animation-timeline
. Визуальный вывод точно такой же, как и раньше.
✨ Попробуйте сами
Создание временной шкалы просмотра прогресса в JavaScript
Чтобы создать временную шкалу просмотра в JavaScript, создайте новый экземпляр класса ViewTimeline
. Передайте пакет свойств с subject
, который вы хотите отслеживать, axis
и inset
.
-
subject
: ссылка на элемент, который вы хотите отслеживать в его собственном скроллере. -
axis
: ось для отслеживания. Как и в варианте CSS, допустимыми значениями являютсяblock
,inline
,x
иy
. -
inset
: настройка вставки (положительная) или начала (отрицательная) области прокрутки при определении того, находится ли поле в поле зрения.
const tl = new ViewTimeline({
subject: document.getElementById('subject'),
});
Чтобы прикрепить его к веб-анимации, передайте его как свойство timeline
и опустите duration
, если таковая имеется. При необходимости передайте информацию о диапазоне, используя свойства rangeStart
и rangeEnd
.
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
rangeStart: 'entry 25%',
rangeEnd: 'cover 50%',
});
✨ Попробуйте сами
Еще больше вещей, которые стоит попробовать
Присоединение к нескольким диапазонам временной шкалы просмотра с помощью одного набора ключевых кадров.
Давайте посмотрим на эту демонстрационную версию списка контактов, где записи списка анимированы. Когда запись списка входит в область прокрутки снизу, она скользит + исчезает, а когда она выходит из области прокрутки вверху, она скользит + исчезает.
✨ Попробуйте сами
В этой демонстрации каждый элемент украшен одной временной шкалой просмотра, которая отслеживает элемент, когда он пересекает область прокрутки, но к нему прикреплены две анимации, управляемые прокруткой. animate-in
прикрепляется к диапазону entry
временной шкалы, а animate-out
к диапазону exit
шкалы времени.
@keyframes animate-in {
0% { opacity: 0; transform: translateY(100%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes animate-out {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-100%); }
}
#list-view li {
animation: animate-in linear forwards,
animate-out linear forwards;
animation-timeline: view();
animation-range: entry, exit;
}
Вместо запуска двух разных анимаций, прикрепленных к двум разным диапазонам, также можно создать один набор ключевых кадров, который уже содержит информацию о диапазоне.
@keyframes animate-in-and-out {
entry 0% {
opacity: 0; transform: translateY(100%);
}
entry 100% {
opacity: 1; transform: translateY(0);
}
exit 0% {
opacity: 1; transform: translateY(0);
}
exit 100% {
opacity: 0; transform: translateY(-100%);
}
}
#list-view li {
animation: linear animate-in-and-out;
animation-timeline: view();
}
Поскольку ключевые кадры содержат информацию о диапазоне, вам не нужно указывать animation-range
. Результат точно такой же, как и был раньше.
✨ Попробуйте сами
Прикрепление к временной шкале прокрутки, не являющейся предком
Механизм поиска для именованных временных шкал прокрутки и именованных временных шкал просмотра ограничен только предками прокрутки. Однако очень часто элемент, который необходимо анимировать, не является дочерним элементом скроллера, который необходимо отслеживать.
Чтобы это работало, в игру вступает свойство timeline-scope
. Вы используете это свойство, чтобы объявить временную шкалу с этим именем, не создавая ее фактически. Это дает временной шкале с таким названием более широкий охват. На практике вы используете свойство timeline-scope
общего родительского элемента, чтобы к нему можно было прикрепить временную шкалу дочернего скроллера.
Например:
.parent {
timeline-scope: --tl;
}
.parent .scroller {
scroll-timeline: --tl;
}
.parent .scroller ~ .subject {
animation: animate linear;
animation-timeline: --tl;
}
В этом фрагменте:
- Элемент
.parent
объявляет временную шкалу с именем--tl
. Любой его дочерний элемент может найти и использовать его в качестве значения свойстваanimation-timeline
. - Элемент
.scroller
фактически определяет временную шкалу прокрутки с именем--tl
. По умолчанию он будет виден только его дочерним элементам, но поскольку.parent
установил его в качествеscroll-timeline-root
, он прикрепляется к нему. - Элемент
.subject
использует временную шкалу--tl
. Он поднимается по дереву предков и находит--tl
в.parent
. Поскольку--tl
в.parent
указывает на--tl
.scroller
,.subject
по сути будет отслеживать временную шкалу прокрутки.scroller
.
Другими словами, вы можете использовать timeline-root
для перемещения временной шкалы к предку (также известно как подъем ), чтобы все дочерние элементы предка могли получить к ней доступ.
Свойство timeline-scope
можно использовать как с прокруткой временной шкалы, так и с просмотром временной шкалы.
Больше демонстраций и ресурсов
Все демоверсии, рассмотренные в этой статье на мини-сайте Scroll-driven-animations.style . На веб-сайте есть еще множество демонстраций, демонстрирующих возможности анимации, управляемой прокруткой.
Одна из дополнительных демо-версий — это список обложек альбомов. Каждая крышка вращается в 3D, попадая в центр внимания.
✨ Попробуйте сами
Или эта демонстрация стопки карт, в которой используется position: sticky
. По мере того, как карты складываются, уже приклеенные карты уменьшаются, создавая приятный эффект глубины. В конце концов, вся стопка как группа исчезает из поля зрения.
✨ Попробуйте сами
На сайте Scroll-driven-animations.style также представлен набор инструментов, таких как визуализация прогресса диапазона временной шкалы, которая была включена ранее в этот пост.
Анимации, управляемые прокруткой, также описаны в разделе «Что нового в веб-анимации» на Google I/O '23.