Эффект параллакса при прокрутке – это визуальный прием, при котором разные слои контента перемещаются с разной скоростью, когда пользователь прокручивает страницу вниз. Это создает иллюзию глубины, когда элементы переднего плана кажутся движущимися быстрее, чем элементы фона, что приводит к увлекательному и захватывающему просмотру. Этого эффекта лучше всего достичь с помощью анимации на основе прокрутки CSS, которая позволяет связать анимацию с положением прокрутки контейнера.
Рекомендации по внедрению
Вот как создать базовый эффект параллакса:
Создайте элемент-контейнер. Он объединяет все слои эффекта параллакса. Это не прокручиваемый элемент, поэтому его переполнение должно быть обрезано. Также задайте для него значение
height, соответствующее высоте одного из слоев эффекта параллакса.<div class="wrapper"> … </div>.wrapper { overflow: clip; height: 100vh; /* Height of one of the layers of the parallax */ }Объявите слои. Внутри контейнера добавьте отдельные слои, которые будут двигаться с разной скоростью.
<div class="wrapper"> <div class="layer">LAYER 0</div> <div class="layer">LAYER 1</div> <div class="layer">LAYER 2</div> … </div>Добавьте анимацию перемещения. Задайте анимацию CSS, которая изменяет свойство
transformслоев. Для создания эффекта параллакса обычно используется свойствоtranslateY, которое перемещает слои по вертикали.@keyframes parallax { from { transform: translateY(700px); } }Настройте
view-timeline. Чтобы связать анимацию с положением прокрутки, создайтеview-timelineдля элемента-контейнера и примените его к слоям..wrapper { view-timeline: --wrapper; } .layer { animation: parallax linear both; animation-timeline: --wrapper; }Настройте анимацию. Чтобы слои двигались с разной скоростью, можно использовать один из двух основных подходов: смещение ключевых кадров или смещение
animation-range.В обоих подходах можно использовать значения, заданные в коде, или реализацию
sibling-index()/sibling-count(). Задать значения в коде проще всего, и это удобно, если слоев немного. Реализацияsibling-index()/sibling-count()удобна, когда у вас много слоев.Смещение ключевых кадров
С помощью заданных в коде значений можно определить дополнительный параметр для каждого слоя, чтобы вручную управлять его смещением.
.layer:nth-child(1) { --offset: 100px; } .layer:nth-child(2) { --offset: 200px; } .layer:nth-child(3) { --offset: 300px; } @keyframes parallax { from { transform: translateY(var(--offset)); } }Используя
sibling-index(), настройте функциюsibling-index()так, чтобы она возвращала индекс дочернего элемента среди его элементов того же уровня для автоматического расчета эффекта задержки.@keyframes parallax { from { transform: translateY(calc(100px * sibling-index())); } }Постепенное внедрение
animation-rangeС помощью заданных в коде значений можно явно определить границы
animation-rangeдля каждого слоя по отдельности..layer:nth-child(1) { animation-range: entry 25% exit 50%; } .layer:nth-child(2) { animation-range: entry 25% exit 75%; } .layer:nth-child(3) { animation-range: entry 25% exit 100%; }Используя
sibling-index()иsibling-count(), вы можете математически рассчитать диапазон на основе общего количества уровней (sibling-count())..layer { animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index()); }
Пример кода
@keyframes parallax {
from {
transform: translateY(calc(100px * sibling-index()));
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
Также вы можете использовать свойство animation-range:
@keyframes parallax {
from {
transform: translateY(700px);
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index());
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
Рекомендации
При использовании анимации, управляемой прокруткой, важно следовать нескольким рекомендациям, чтобы обеспечить плавность и доступность:
- РЕКОМЕНДУЕТСЯ включить обнаружение функций. Не все браузеры поддерживают анимацию, управляемую прокруткой. Используйте
@supports ((animation-timeline: view()) and (animation-range: entry)), чтобы проверить поддержку и предоставить резервный вариант для браузеров, которые ее не поддерживают.- Проверка
(animation-range: entry)ОБЯЗАТЕЛЬНО должна быть включена, чтобы отфильтровать браузеры с частичной поддержкой. - НЕ ИСПОЛЬЗУЙТЕ пакет
scroll-timeline-polyfillдля резервной стратегии, так как он неполный и имеет много известных проблем. - Если анимация используется только в декоративных целях, выберите прогрессивное улучшение и НЕ добавляйте резервный вариант.
- Проверка
- Уважайте предпочтения пользователей. Некоторые пользователи предпочитают, чтобы на веб-страницах было меньше движения. Используйте медиазапрос
prefers-reduced-motion, чтобы отключить или уменьшить анимацию для этих пользователей. - РЕКОМЕНДУЕТСЯ анимировать только эффективные свойства CSS. Чтобы анимация была максимально плавной, анимируйте свойства, которые могут обрабатываться потоком компоновщика браузера, например
transformиopacity. Анимация других свойств, таких какwidthилиheight, может привести к проблемам производительности. - Правильно указывайте порядок объявления. При использовании сокращенного свойства
animationобъявляйтеanimation-timelineиanimation-rangeпосле него, чтобы сокращенное свойство не сбрасывало временную шкалу.
Чтобы задать animation-range:
- РЕКОМЕНДУЕТСЯ задавать для всех слоев одинаковое смещение начала, например
entry 25%. - РЕКОМЕНДУЕТСЯ задавать для всех слоев разные конечные смещения, используя
sibling-count()иsibling-index()для распределения смещений, напримерexit calc(100% / sibling-count() * sibling-index()).
Поддержка браузеров и резервные стратегии
Поэтому обычно требуется резервная стратегия.
Для браузеров, которые не поддерживают анимацию на основе прокрутки, можно использовать резервный вариант, чтобы воссоздать визуальные эффекты. Резервные варианты обычно создаются с помощью прослушивателя прокрутки (для эффектов ScrollTimeline) или IntersectionObserver API (для эффектов ViewTimeline).
В браузерах со встроенной поддержкой анимации на основе прокрутки ВСЕГДА используйте нативную реализацию CSS, так как она более эффективна.
Обратите внимание, что не все эффекты можно воссоздать с помощью резервных объявлений.
В частности, для этого варианта использования следующий скрипт применяет резервный вариант для браузеров, которые не поддерживают анимацию с прокруткой. Он использует IntersectionObserver для отслеживания видимости элемента .wrapper и обновляет свойство transform слоев в зависимости от положения прокрутки.
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const wrapper = document.querySelector('.wrapper');
const layers = document.querySelectorAll('.layer');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
window.addEventListener('scroll', onScroll);
} else {
window.removeEventListener('scroll', onScroll);
}
});
}, { threshold: 0 });
observer.observe(wrapper);
function onScroll() {
const scrollY = window.scrollY;
const wrapperRect = wrapper.getBoundingClientRect();
const wrapperTop = wrapperRect.top + scrollY;
const wrapperHeight = wrapperRect.height;
const windowHeight = window.innerHeight;
if (scrollY >= wrapperTop - windowHeight && scrollY <= wrapperTop + wrapperHeight) {
const scrollPercent = (scrollY - (wrapperTop - windowHeight)) / (wrapperHeight + windowHeight);
layers.forEach((layer, index) => {
// This matches the effect as defined in the CSS example above.
// Customize this further if needed.
const initialTranslateY = 100 * index;
const translateY = initialTranslateY * (1 - scrollPercent);
layer.style.transform = `translateY(${translateY}px)`;
});
}
}
// Trigger onScroll once to set initial positions
onScroll();
}