Узнайте, как работать с временными шкалами прокрутки и временными шкалами представления для создания анимации, управляемой прокруткой, декларативным способом.
Опубликовано: 5 мая 2023 г.
Анимация, управляемая прокруткой
Анимация, управляемая прокруткой, — распространённый шаблон UX в веб-разработке. Анимация, управляемая прокруткой, связана с положением прокрутки контейнера. Это означает, что при прокрутке вверх или вниз связанная анимация перемещается вперёд или назад в ответ на это. Примерами таких эффектов являются параллаксные фоновые изображения или индикаторы чтения, которые перемещаются при прокрутке.
Аналогичный тип анимации, управляемой прокруткой, — это анимация, связанная с положением элемента внутри контейнера прокрутки. В ней, например, элементы могут плавно появляться в поле зрения.
Классический способ достижения подобных эффектов — это реагирование на события прокрутки в основном потоке , что приводит к двум основным проблемам:
- Современные браузеры выполняют прокрутку в отдельном процессе и, следовательно, передают события прокрутки асинхронно.
- Анимация в основном потоке подвержена рывкам .
Это делает создание высокопроизводительных анимаций, синхронизированных с прокруткой, невозможным или очень сложным.
Начиная с версии Chrome 115, появился новый набор API и концепций, которые можно использовать для включения декларативных анимаций, управляемых прокруткой: временные шкалы прокрутки и временные шкалы представления.
Эти новые концепции интегрируются с существующими API веб-анимаций (WAAPI) и API CSS-анимаций , позволяя им наследовать преимущества, которые предоставляют эти существующие 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) .
Демонстрация: Индикатор прогресса чтения
В этой демонстрации индикатор прогресса чтения закреплен в верхней части области просмотра. По мере прокрутки страницы вниз полоса прогресса увеличивается, пока не займет всю ширину области просмотра, достигнув конца документа. Для управления анимацией используется анонимная временная шкала прогресса прокрутки.
✨ Попробуйте сами
Индикатор прогресса чтения расположен в верхней части страницы с помощью свойства `position: fixed`. Для использования составных анимаций анимируется не 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% нацелены на одну и ту же область.
Когда параметры range-start и range-end указывают на одно и то же имя диапазона и охватывают весь диапазон — от 0% до 100% — значение можно сократить до простого имени диапазона. Например, animation-range: entry 0% entry 100%; можно переписать на гораздо более короткий animation-range: entry .
Демонстрация: показ изображения
В этой демонстрации изображения плавно появляются по мере их появления в области прокрутки. Это достигается с помощью анонимной временной шкалы просмотра. Диапазон анимации был настроен таким образом, что каждое изображение становится полностью непрозрачным, когда оно находится посередине полосы прокрутки.
✨ Попробуйте сами
Эффект расширения достигается с помощью анимированного контура обрезки (clip-path). Для этого эффекта используется следующий 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%',
});
✨ Попробуйте сами
Ещё больше вещей, которые стоит попробовать
Прикрепление к нескольким диапазонам временной шкалы просмотра с помощью одного набора ключевых кадров.
Давайте посмотрим на демонстрацию списка контактов, где элементы списка анимированы. Когда элемент списка появляется в области прокрутки снизу, он плавно появляется и исчезает, а когда он исчезает из области прокрутки сверху, он плавно исчезает.
✨ Попробуйте сами
В этой демонстрации каждый элемент помечается одной временной шкалой View Timeline, которая отслеживает элемент при пересечении области прокрутки, но к ней прикреплены две анимации, управляемые прокруткой. Анимация 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.