Efekt paralaksy podczas przewijania to technika wizualna, w której różne warstwy treści poruszają się z różną szybkością, gdy użytkownik przewija stronę w dół. Tworzy to iluzję głębi, ponieważ elementy na pierwszym planie wydają się poruszać szybciej niż elementy w tle, co zapewnia angażujące i wciągające wrażenia podczas przeglądania. Najlepszy efekt można osiągnąć, używając animacji CSS opartych na przewijaniu, które umożliwiają powiązanie animacji z pozycją przewijania kontenera.
Jak to zrobić?
Aby utworzyć podstawowy efekt paralaksy:
Utwórz element opakowujący: ten element po prostu grupuje wszystkie warstwy efektu paralaksy. Nie jest to element przewijany, więc jego przepełnienie powinno zostać przycięte. Ustaw też
height, która odpowiada wysokości jednej z warstw efektu paralaksy.<div class="wrapper"> … </div>.wrapper { overflow: clip; height: 100vh; /* Height of one of the layers of the parallax */ }Zadeklaruj warstwy: w elemencie wrapper dodaj poszczególne warstwy, które będą się poruszać z różną szybkością.
<div class="wrapper"> <div class="layer">LAYER 0</div> <div class="layer">LAYER 1</div> <div class="layer">LAYER 2</div> … </div>Dodaj animację przesunięcia: zdefiniuj animację CSS, która zmienia właściwość
transformwarstw. Aby uzyskać efekt paralaksy, zwykle używa się właściwościtranslateYdo przesuwania warstw w pionie.@keyframes parallax { from { transform: translateY(700px); } }Skonfiguruj
view-timeline: aby połączyć animację z pozycją przewijania, utwórzview-timelinew elemencie opakowania, a następnie zastosuj go do warstw..wrapper { view-timeline: --wrapper; } .layer { animation: parallax linear both; animation-timeline: --wrapper; }Przesunięcie animacji: aby warstwy poruszały się z różnymi prędkościami, możesz użyć jednego z 2 głównych podejść: przesunięcia w klatkach kluczowych lub przesunięcia
animation-range.Obie metody mogą używać wartości zakodowanych na stałe lub implementacji
sibling-index()/sibling-count(). Wartości zakodowane na stałe są najłatwiejsze i przydatne, gdy masz tylko ograniczoną liczbę warstw. Implementacjasibling-index()/sibling-count()jest przydatna, gdy masz wiele warstw.Staggering in the keyframes:
Za pomocą wartości zakodowanych na stałe możesz zdefiniować właściwość niestandardową dla każdej warstwy, aby ręcznie kontrolować jej przesunięcie paralaksy.
.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)); } }Za pomocą
sibling-index()możesz sprawić, że funkcjasibling-index()będzie zwracać indeks elementu podrzędnego wśród jego elementów równorzędnych, aby automatycznie obliczać efekt kaskadowy.@keyframes parallax { from { transform: translateY(calc(100px * sibling-index())); } }Staggering the
animation-range:Za pomocą wartości zakodowanych na stałe możesz wyraźnie określić granice
animation-rangena każdej warstwie osobno..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%; }Za pomocą
sibling-index()isibling-count()możesz obliczyć zakres matematycznie na podstawie łącznej liczby warstw (sibling-count())..layer { animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index()); }
Przykładowy kod
@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;
}
}
Możesz też użyć właściwości animation-range, aby uzyskać podobny efekt:
@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;
}
}
Sprawdzone metody
Podczas korzystania z animacji sterowanych przewijaniem warto stosować kilka sprawdzonych metod, aby zapewnić płynne i dostępne działanie:
- DO include feature detection: Not all browsers support scroll-driven animations. Użyj
@supports ((animation-timeline: view()) and (animation-range: entry)), aby sprawdzić, czy jest obsługiwany, i zapewnić rozwiązanie zastępcze w przypadku przeglądarek, które go nie obsługują.- MUSISZ uwzględnić tutaj sprawdzenie
(animation-range: entry), aby odfiltrować przeglądarki, które obsługują tylko częściowo. - NIE używaj pakietu
scroll-timeline-polyfillw strategii zastępczej, ponieważ nie jest on kompletny i ma wiele znanych problemów. - Jeśli animacja ma charakter wyłącznie dekoracyjny, wybierz progresywne ulepszanie i NIE podawaj kreacji zastępczej.
- MUSISZ uwzględnić tutaj sprawdzenie
- DO respect user preferences: Some users prefer to have less motion on the web. Użyj zapytania o media
prefers-reduced-motion, aby wyłączyć lub ograniczyć animacje dla tych użytkowników. - DO try to animate only performant CSS properties: For the smoothest animations, stick to animating properties that can be handled by the browser's compositor thread, such as
transformandopacity. Animowanie innych właściwości, takich jakwidthlubheight, może powodować problemy z wydajnością. - DO use the correct declaration order: When using the
animationshorthand property, declareanimation-timelineandanimation-rangeafter it to prevent the shorthand from resetting the timeline.
Jeśli chodzi o ustawienie animation-range:
- DO give all layers the same start offset, e.g.
entry 25% - DO give all layers a different end offset that uses
sibling-count()andsibling-index()to distribute the offsets, e.g.exit calc(100% / sibling-count() * sibling-index()).
Obsługa przeglądarek i strategie zastępcze
Dlatego zwykle wymagana jest strategia zastępcza.
W przypadku przeglądarek, które nie obsługują animacji sterowanych przewijaniem, możesz użyć kreacji zastępczej, aby odtworzyć efekty wizualne. Kreacje zastępcze są zwykle tworzone za pomocą detektora przewijania (w przypadku efektów ScrollTimeline) lub interfejsu IntersectionObserver API (w przypadku efektów ViewTimeline).
W przeglądarkach z wbudowaną obsługą animacji opartych na przewijaniu ZAWSZE używaj natywnej implementacji CSS, ponieważ jest ona bardziej wydajna.
Pamiętaj, że nie każdy efekt można odtworzyć za pomocą kreacji zastępczych.
W tym konkretnym przypadku poniższy skrypt stosuje kreację zastępczą w przeglądarkach, które nie obsługują animacji sterowanych przewijaniem. Używa IntersectionObserver do śledzenia widoczności elementu .wrapper i aktualizuje właściwość transform warstw na podstawie pozycji przewijania.
// 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();
}