捲動視差效果是一種視覺技術,當使用者向下捲動網頁時,不同層的內容會以不同速度移動。這會產生深度錯覺,前景元素看起來比背景元素移動得更快,進而帶來引人入勝且身歷其境的瀏覽體驗。如要達到最佳效果,建議使用 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();
}