В этом пошаговом руководстве будет рассмотрено создание CSS-анимации с нуля и её включение при скролле (прокрутке вверх-вниз) страницы, когда HTML-элемент находится в поле зрения – видимой части окна, с помощью Intersection Observer API.
Настройка CSS-анимации с помощью ключевых кадров
Начнем с создания CSS-анимации, которая определяется ключевыми кадрами @keyframes
.
@keyframes wipe-enter { 0% { transform: scale(0, .025); } 50% { transform: scale(1, .025); } }
Анимацию можно назвать как угодно, пусть будет wipe-enter
. Здесь HTML-элемент будет увеличиваться в ширину, а затем в высоту.
После того, как определены ключевые кадры, можно использовать анимацию для элемента, установив свойству animation-name
название ключевых кадров: wipe-enter
. Кроме этого необходимо установить animation-duration
– продолжительность анимации и animation-iteration-count
– счетчик итераций, указывающий, сколько раз анимация должна воспроизводиться, например – бесконечно.
@media (prefers-reduced-motion: no-preference) {
.square {
animation-name: wipe-enter;
animation-duration: 1s;
animation-iteration-count: infinite;
}
}
CSS-класс .square
пусть будет заключён в медиа-запрос prefers-reduce-motion: no-preference
. Это обеспечит запуск анимации только в том случае, если пользователь не ограничил её и не включил параметр «уменьшить движение» в своей операционной системе.
В этом примере будет анимирован HTML-элемент квадратной формы:
Для CSS-анимации можно использовать сокращённое написание свойства animation
, например:
@media (prefers-reduced-motion: no-preference) {
.square {
animation: wipe-enter 1s infinite;
}
}
Управление анимацией с помощью CSS-класса
Допустим, не нужно, чтобы анимация воспроизводилась сразу. Воспроизведением анимации можно управлять, добавляя HTML-элементу CSS-класс переключатель только анимации, который не используется для остальной стилизации элемента.
.square {
width: 200px;
height: 200px;
background: #e62a52;
// и т.д.
}
@media (prefers-reduced-motion: no-preference) {
.square-animation {
animation: wipe-enter 1s 1;
}
}
CSS-анимация будет воспроизводиться, когда HTML-элементу будет добавлен CSS-класс .square-animation
:
В этом примере CSS-анимация воспроизводится не каждый раз при нажатии на кнопку, а только тогда, когда HTML-элементу будет добавлен CSS-класс .square-animation
.
Несмотря на то, что анимация должна показывать появление элемента, элемент отображается и до добавления класса .square-animation
. Это сделано для того, чтобы при первой загрузке элемент был виден пользователю, даже если JavaScript заблокирован или не работает.
Добавление CSS-класса, когда элемент появляется при скролле (прокрутке страницы вверх-вниз)
В примере выше была реализована CSS-анимация, которая запускается при добавлении HTML-элементу CSS-класса. Вместо перехвата события нажатия кнопки, для добавления и удаления CSS-класса можно использовать несколько вариантов обнаружения состояния, когда элемент при скролле появляется в видимой части окна.
Вот три способа определить, когда элемент находится в видимой области окна:
- Использовать Intersection Observer API
- Измерять смещение элемента при скролле страницы
- Использовать стороннюю JavaScript-библиотеку, которая реализует №1 или №2
Для базовой анимации с запуском при скролле, оптимально использование Intersection Observer API, потому что он требует меньше кода, удобнее и лучше с точки зрения производительности.
API-интерфейс Intersection Observer позволяет отслеживать момент пересечения одного элемента с другим, и сообщает, когда это происходит. Этот способ идеально подходит для запуска CSS-анимации при скролле страницы. Всё, что нужно знать – когда HTML-элемент пересекается с окном просмотра. Если он пересекается, значит – находится в видимой области окна и в этот момент надо запустить CSS-анимацию.
Intersection Observer API можно рассматривать, как обычный слушатель событий, но с некоторыми дополнительными опциями. Вместо того, чтобы прикреплять прослушивание событий к HTML-элементу, надо заставить наблюдателя отслеживать элемент и его положение на странице.
Начнём с создания наблюдателя и заставим его отслеживать HTML-элемент:
// Создать наблюдателя
const observer = new IntersectionObserver(entries => {
// Заполним метод обратного вызова позже...
});
// Сообщить наблюдателю, какие элементы следует отслеживать
observer.observe(document.querySelector('.square'));
По умолчанию корневым элементом, который будет проверяться на пересечение, является окно браузера, поэтому наблюдателю нужно только сообщить об анимируемом HTML-элементе.
Когда функция обратного вызова (callback) запускается, она возвращает массив записей из целевых (target) элементов, которые были запрошены, а также некоторую дополнительную информацию о них. В функцию всегда будет возвращаться массив, даже если наблюдение ведётся только за одним элементом, как здесь.
В функции обратного вызова можно перебрать массив записей, чтобы указать, что с ними нужно сделать. Каждая запись имеет свойство isIntersecting
, которое может быть true
или false
. Если оно возвращает true
, это означает, что элемент находится в видимой области окна (viewport).
entries.forEach(entry => {
if (entry.isIntersecting) {
// Элемент появился,
// надо добавить CSS-класс для анимации
}
});
Собираем всё вместе. Обратите внимание, что entry
– это объект, предоставленный наблюдателем, а entry.target
– это фактический элемент, за которым который ведется наблюдение, поэтому именно ему нужно добавить CSS-класс для запуска анимации.
const observer = new IntersectionObserver(entries => {
// перебор записей
entries.forEach(entry => {
// если элемент появился
if (entry.isIntersecting) {
// добавить ему CSS-класс
entry.target.classList.add('square-animation');
}
});
});
observer.observe(document.querySelector('.square'));
Теперь, когда HTML-элемент пересекает границы окна браузера, ему будет добавлен CSS-класс, который будет воспроизводить анимацию.
Если нужно, чтобы анимация запускалась каждый раз, когда HTML-элемент входит в видимую область окна, необходимо удалять CSS-класс запуска анимации, когда он находится за пределами видимой области окна.
Если элемент при анимации изменяет размер или положение, браузеру может быть сложно решить, находится ли элемент в данный момент в области просмотра или нет. Лучше всего поместить анимируемый элемент в контейнер, который не изменяет размер или положение и использовать его для наблюдения за скролллом.
<div class="square-wrapper">
<div class="square"></div>
</div>
Теперь надо наблюдать за HTML-элементом c CSS-классом square-wrapper
а класс для анимации применять к элементу с классом square
, как и прежде. Когда элемент-оболочка находится за пределами видимой области, нужно удалять CSS-класс у элемента .square
, чтобы анимация перезапускалась каждый раз, когда элемент появляется в окне при скролле.
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const square = entry.target.querySelector('.square');
if (entry.isIntersecting) {
square.classList.add('square-animation');
return; // если класс добавлен, продолжать уже не надо
}
// перемещение завершено, теперь надо удалить класс
square.classList.remove('square-animation');
});
});
observer.observe(document.querySelector('.square-wrapper'));
Чтобы элемент-оболочку было видно, для примера, ему добавлена пунктирная рамка. Попробуйте прокрутить вверх и вниз документ в окне ниже:
Теперь – порядок! Добавляя и удаляя CSS-класс каждый раз, когда при скролле страницы HTML-элемент входит в область просмотра, запускается CSS-анимация.
Запуск CSS-transition при скролле
Если для анимации используется только один шаг и не требуется применение @keyframes
, например, достаточно изменения прозрачности HTML-элемента от 0 до 1, можно использовать CSS-transition вместо CSS-animation.
Метод по сути тот же. Вместо определения ключевых кадров (@keyframes
) для CSS-класса анимации указаны свойства для transition
.
.square {
width: 200px;
height: 200px;
background: #e62a52;
border-radius: 8px;
opacity: 0;
transform: scale(1.2);
}
@media (prefers-reduced-motion: no-preference) {
.square {
transition: opacity 1.5s ease, transform 1.5s ease;
}
}
.square-transition {
opacity: 1;
transform: none;
}
Заставим HTML-элемент появляться, когда он перемещается в видимую область просмотра. Для этого в момент появления в окне при скролле ему надо добавить CSS-класс square-transition
. Так пользователь, у которого заблокирован или не загружается JavaScript, должен всё-равно увидеть HTML-элемент в его окончательном состоянии.
<div class="square-wrapper">
<div class="square square-transition"></div>
</div>
Это особенно важно, поскольку анимация начинается с opacity: 0
. Если бы не было настройки CSS-класса square-transition
и JavaScript не работал, пользователь вообще не увидел бы HTML-элемент! Но если эффект перехода заключается в том, чтобы что-то исчезло, наверное, это не понадобится делать.
При первом запуске JavaScript нужно удалить CSS-класс, чтобы затем, его можно было добавить обратно, когда действительно нужно запустить transition
. Это должно быть сделано вне метода обработки наблюдения за скроллом, желательно в начале JavaScript. Вот полный код:
// Удалить CSS-класс square-transition
const square = document.querySelector('.square');
square.classList.remove('square-transition');
// Добавить наблюдение за появлением элемента
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
square.classList.add('square-transition');
return;
}
square.classList.remove('square-transition');
});
});
observer.observe(document.querySelector('.square-wrapper'));
В этом примере CSS-transition запускается, когда элемент-оболочка при скролле появляется в окне. Если Javascript не запустился, элемент всё-равно будет виден.
Как видите, эту технику можно расширять разными способами, чтобы создать множество эффектов анимации.
How to trigger a CSS animation on scroll.
Фиксирующаяся при скролле панель навигации, только CSS.
Sticky sidebar (VanillaJS).
CSS: современный способ оформления скроллбаров.