視差滾動(parallax scrolling)是讓不同圖層以不同速度跟著滾動移動,背景走得慢、前景走得快,畫面就有前後的距離感。做法大致分三類:純 CSS 的 3D 透視、CSS 滾動驅動動畫,以及用 JavaScript 讀取滾動進度。本文依序說明,每種做法都附可以直接貼上的程式碼,最後整理 WebberUI 裡現成的視差與滾動元件。
做法一:CSS perspective 與 translateZ
這是最早的純 CSS 視差。滾動容器設定 perspective,圖層用 translateZ 推到不同深度,離鏡頭越遠的圖層滾動時移動越少。
.parallax {
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
perspective: 1px;
}
.parallax__layer {
position: absolute;
inset: 0;
}
.parallax__layer--back {
/* 往後推 1px,再放大 2 倍抵銷透視造成的縮小 */
transform: translateZ(-1px) scale(2);
}
.parallax__layer--base {
transform: translateZ(0);
}放大倍率是 1 + (-translateZ) / perspective,上例為 1 + 1 / 1 = 2。
這個做法不需要 JavaScript,但有兩個限制:
- 必須讓
.parallax容器負責滾動,不能用整個頁面的滾動。頁面就多了一層巢狀滾動,滾動位置的記憶、錨點跳轉都要另外處理。 - 設定了
perspective的元素會成為position: fixed子元素的定位基準,裡面的固定導覽列會跟著容器走。
做法二:CSS 滾動驅動動畫
CSS 的 滾動驅動動畫(scroll-driven animations) 把一般的 @keyframes 綁到滾動進度上。scroll() 追蹤滾動容器的整體進度,view() 追蹤某個元素經過可視範圍的進度,做視差通常用 view()。
@keyframes parallax-shift {
from { transform: translateY(-15%); }
to { transform: translateY(15%); }
}
@supports (animation-timeline: view()) {
.parallax-image {
animation: parallax-shift linear both;
/* 必須寫在 animation 簡寫之後,否則會被簡寫重設 */
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
}
@media (prefers-reduced-motion: reduce) {
.parallax-image {
animation: none;
}
}animation-range: cover 0% cover 100% 表示從元素頂端進入畫面,到底端離開畫面為止。
瀏覽器支援度依 caniuse(2026-10-14 查核):
| 瀏覽器 | 支援狀況 |
|---|---|
| Chrome、Edge | 115 起支援 |
| Safari(macOS 與 iOS) | 26.0 起支援 |
| Firefox | 目前正式版與 Android 版尚未支援,caniuse 列到 160 版才標為支援 |
MDN 目前也還沒有把這個功能列為 Baseline。Firefox 使用者佔比不能忽略的話,用 @supports 包起來,不支援的瀏覽器顯示靜態版面即可。視差本來就是裝飾,沒有動畫不影響閱讀。
做法三:Motion 的 useScroll 與 useTransform
React 專案最常用的是 Motion(套件名稱 motion,從 motion/react 匯入)。useScroll 回傳 0 到 1 的滾動進度,useTransform 把進度換算成位移。
"use client";
import { useRef } from "react";
import { motion, useReducedMotion, useScroll, useTransform } from "motion/react";
export function ParallaxImage({ src, alt }: { src: string; alt: string }) {
const ref = useRef<HTMLDivElement>(null);
const reduceMotion = useReducedMotion();
// 元件底部進入畫面時為 0,頂部離開畫面時為 1
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"],
});
// 圖片高度 140%,上下各多 20%,位移 ±10% 不會露出空白
const y = useTransform(
scrollYProgress,
[0, 1],
reduceMotion ? ["0%", "0%"] : ["-10%", "10%"],
);
return (
<div ref={ref} className="relative h-[60vh] overflow-hidden">
<motion.img
src={src}
alt={alt}
style={{ y }}
className="absolute inset-x-0 -top-[20%] h-[140%] w-full object-cover"
/>
</div>
);
}offset 的格式是「目標的位置 容器的位置」,"start end" 代表目標的頂端碰到可視範圍的底部。依 Motion 文件,當進度透過 useTransform 只套用到 transform、opacity、clipPath 或 filter 時,useScroll 會盡量改用瀏覽器原生的 ScrollTimeline 執行,減少滾動時的主執行緒負擔。
效能
- 只動
transform與opacity。 用top、margin或background-position做視差,每一幀都會觸發重新排版或重繪。 - 不要在 scroll 事件裡呼叫
setState。 每次滾動都會重新渲染整棵子樹。Motion 的 motion value 不經過 React 渲染,是比較安全的寫法。 will-change少量使用。 MDN 建議把它當成最後手段。每個加上will-change: transform的元素都可能被提升成獨立圖層,一頁十幾張全幅圖片都加,手機記憶體會先撐不住。- 圖片先壓縮。 視差圖層通常比容器大 30% 到 40%,尺寸要算進去,但不要直接放原始大圖。
減少動態效果
視差會讓部分有前庭障礙的使用者頭暈或噁心。作業系統的「減少動態效果」設定會反映在 CSS 的 prefers-reduced-motion 上,React 裡可以用 Motion 的 useReducedMotion() 讀取。使用者開啟這個設定時,把位移設為 0 或直接改成靜態版面。WCAG 2.3.3「互動引發的動畫」(AAA 等級)也要求這類非必要的動畫可以關閉。
手機上的注意事項
- 不要依賴
background-attachment: fixed。 caniuse 把 iOS Safari 對這個屬性的支援列為「部分支援」,用它做的固定背景視差,在 iPhone 上常常不會如預期固定。 - 網址列伸縮會改變
100vh。 視差區塊的高度改用svh或dvh,避免滾動到一半畫面跳動。 - 幅度要比桌機小。 手機螢幕窄、滾動速度快,同樣的位移量看起來更劇烈,可以用 media query 或依視窗寬度縮小位移。
WebberUI 的視差與滾動元件
如果不想從頭處理上面這些細節,WebberUI 有幾個現成的 React 元件,都用 Motion 實作,也都處理了減少動態效果。WebberUI 的元件可取得原始碼,安裝後檔案會放進你的 components/ui/,可以直接修改。
Parallax Section:多層視差區塊
parallax-section 用 ParallaxSection 包住數個 ParallaxLayer,每層用 speed 設定位移倍率:正值偏前景、負值偏後景。可以傳 container 給巢狀滾動容器使用;系統開啟減少動態效果時,各層停在原位。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add parallax-sectionnpx webberui@latest add https://webberui.com/r/parallax-section.jsonimport {
ParallaxLayer,
ParallaxSection,
} from "@/components/ui/parallax-section";
<ParallaxSection className="h-[420px] bg-neutral-950">
{/* 後景:慢速下沉 */}
<ParallaxLayer decorative speed={-0.6} className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-b from-indigo-500/30 to-transparent" />
</ParallaxLayer>
{/* 前景:快速上浮並淡出 */}
<ParallaxLayer
speed={1.2}
fade
className="absolute inset-0 flex items-center justify-center"
>
<h2 className="text-4xl font-bold text-white">多層視差</h2>
</ParallaxLayer>
</ParallaxSection>;Zoom Parallax:滾動放大穿越
zoom-parallax 撐出一段滾動軌道(預設 h-[300vh]),中間的舞台用 sticky 固定在畫面上,往下滾動時圖片以中心為原點放大,外圍的圖片放大得更快。減少動態效果開啟時改成靜態圖格。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add zoom-parallaxnpx webberui@latest add https://webberui.com/r/zoom-parallax.json其他相關元件
| 元件 | 用途 |
|---|---|
| Parallax Depth Cards | 多層卡片以不同速度位移、縮放與模糊,適合作品或功能展示 |
| Hero Parallax Grid | 多列反向視差的產品或作品牆,進場帶 3D 傾斜 |
| Horizontal Scroll Section | 用直向滾動驅動橫向位移的固定區段 |
| Sticky Reveal | 左側文字隨滾動流動,右側視覺固定並交叉切換 |
| Image Sequence Scrub | 以滾動進度逐格播放影格序列,常見於產品頁 |
| Velocity Skew | 依滾動速度傾斜與模糊內容,停下時回正 |
完整清單見滾動效果分類。
怎麼選
- 只要一張背景圖輕微位移,Firefox 不支援也沒關係:用 CSS 滾動驅動動畫,不必載入 JavaScript。
- 需要跨瀏覽器一致、或要配合 React 狀態:用 Motion 的
useScroll。 - 要多層景深、巢狀滾動容器、減少動態效果都處理好:直接安裝
parallax-section或zoom-parallax,再依設計調整。