網路架站

View Transitions API 是什麼?不用改 SPA,CSS 就能做換頁轉場

多數人以為,網頁要做出電影般的換頁動畫,網站架構就得先整個打掉重練,改成用 JavaScript 框架接管路由的單頁應用(SPA)。其實不必這麼大費周章,瀏覽器本身就內建了這個功能,叫做 View Transitions API(畫面轉場 API),而且完全不用寫一行 JavaScript。

只要在樣式表加上幾行 CSS 宣告,一般的多頁式網站(MPA)點連結換頁時,畫面就會自動跑交叉淡出淡入,不再是舊文件卸載到新文件載入完成之間,硬生生泛白或泛黑的那段空檔。這對台灣大量還在用 WordPress 這類多頁架構的網站是個好消息,不必砍掉重練成 SPA,也不用裝一支笨重的動畫外掛,就能拿到接近原生 App 的順暢切換體感。

傳統多頁式網站換頁時,卸載舊文件到載入新文件之間會露出一段沒有補間畫面的泛白空檔;啟用 View Transitions 後改成舊畫面淡出、新畫面淡入的交叉補間,中間不再泛白。
傳統換頁在卸載舊文件、載入新文件之間會泛白,View Transitions 用一段交叉補間把這段空檔補起來。

換頁瞬間泛白,其實是瀏覽器換頁的原生留白

點一個連結換頁,畫面閃過一下空白甚至純黑,再跳出新頁面,這不是網站設計出了問題,是瀏覽器處理換頁這件事時本來就會有的空檔。瀏覽器收到導覽請求後,會先把目前這份文件整個卸載,包括它的 DOM 樹、樣式與 JavaScript 狀態全部清空,接著才去抓取新的 HTML、重新解析、重新繪製畫面。舊文件被丟掉、新文件還沒解析完成的這段期間,瀏覽器沒有任何補間畫面可以顯示,只能露出瀏覽器背景色,通常就是那片白。

過去要避開這段空白,幾乎只有一條路,把整個網站改造成單頁應用。SPA 的做法是讓瀏覽器只完整載入一次頁面骨架,之後每次換頁其實都是用 JavaScript 動態抽換畫面裡的一小塊 DOM,瀏覽器從頭到尾沒有真正卸載過文件,自然也就沒有那段空白可言。轉場動畫就是在這次抽換前後,用框架自己控制淡出淡入或滑動效果。這也是網站要做順暢轉場,一定要上 SPA 框架這個說法的由來,過去確實只有這條路能走。

不過瀏覽器這幾年已經把這件事內建進來了,一般的多頁式網站不用改架構、不用裝框架,也能拿到同等順暢轉場的原生做法。

View Transitions API 是什麼?瀏覽器內建的轉場快照機制

View Transitions API(畫面轉場 API)是由全球資訊網協會與網頁孵化社群小組(W3C/WICG)共同推動、目前由主流瀏覽器原生實作的網頁平台功能。運作邏輯很直觀,每次換頁或換狀態的那一瞬間,瀏覽器會自動拍下「舊畫面」與「新畫面」兩張快照,再用普通的 CSS Animation 在這兩張快照之間跑轉場,預設效果是淡出淡入。整個過程不需要安裝任何 JavaScript 框架,也不必把網站改成單頁應用,原本是純 HTML 的多頁式網站照樣能用。

這個 API 其實分成兩個層級,而且目前的成熟度不一樣。一個是給單頁應用用的「同文件轉場」,另一個是給多頁式網站用的「跨文件轉場」,兩者的觸發方式與支援度都不同。跨文件轉場才是一般 WordPress 這類多頁式網站真正用得到的那一半。

同文件轉場與跨文件轉場,多頁網站要用的是後者

同文件轉場(same-document view transition)是設計給單頁應用用的,網頁的 JavaScript 呼叫 document.startViewTransition(),在同一份文件裡手動更新 DOM,例如切換某個區塊的內容或換一個分頁,瀏覽器接手拍照與補動畫,開發者只需要負責更新 DOM 這一步。這條路徑一直都要寫 JavaScript,跟框架時代的做法差異不大,只是省掉了自己接管動畫的力氣。

跨文件轉場(cross-document view transition)不需要呼叫任何 API,使用者點一個普通的 <a> 連結,瀏覽器導覽到另一個 HTML 檔案,只要兩邊都宣告了對應的 CSS,轉場就會自動觸發,完全不必寫任何 JavaScript。這代表現有的 WordPress、靜態網站、傳統伺服器渲染的網站,不需要改一行後端邏輯,光靠 CSS 就能拿到轉場效果。這也是為什麼跨文件轉場比同文件轉場更值得一般網站關注,它幾乎是零成本的升級。

同文件轉場給單頁應用用,要呼叫 document.startViewTransition() 並手動更新 DOM;跨文件轉場給多頁式網站用,點普通連結就自動觸發、兩邊宣告 CSS 即可,完全不用寫 JavaScript。
View Transitions API 分兩個層級,一般多頁式網站真正用得到的是不必寫 JavaScript 的跨文件轉場。

啟用跨文件轉場只需要一段 CSS 宣告

要打開跨文件轉場,做法比想像中單純,在前後兩個頁面都會載入到的樣式表裡,加上一段 @view-transition 規則。

@view-transition {
  navigation: auto;
}Code language: CSS (css)

只要這段 CSS 生效,使用者點連結從一個頁面換到另一個頁面時,瀏覽器就會自動跑一次交叉淡出淡入,不需要再寫任何 JavaScript 去監聽換頁事件。對大部分網站來說,這段 CSS 放進全站共用的主要樣式表就夠了,因為多數 CMS 或框架產出的頁面本來就會共用同一份 CSS。

有一個容易被忽略的條件,出發頁和到達頁都要宣告這段 CSS,轉場才會生效。只有出發頁有宣告、到達頁沒有,或反過來,瀏覽器會判定條件不成立,直接跳過轉場,換頁功能照常運作,只是少了動畫。這個特性也可以反過來利用,例如刻意讓登入頁、付款頁不宣告這段 CSS,讓使用者在這些敏感頁面之間切換時維持乾淨俐落的直接跳轉,不套用轉場效果。

另外要留意,navigation: auto 只對同源、且使用者主動觸發的導覽生效。導覽到別的網域的連結、表單送出後的 POST 提交、或是用 JavaScript 寫的 location.href = ... 這種程式化導覽,都不會觸發轉場。

舊版的 meta 標籤寫法已經廢止不再生效

網路上還流傳著一種更早期的寫法,在 <head> 放一段 meta 標籤來宣告啟用跨文件轉場。

<meta name="view-transition" content="same-origin">Code language: HTML, XML (xml)

這個寫法已經被廢止。Chrome 從 126 版開始,改成前一節介紹的 @view-transition CSS at-rule 寫法,舊的 meta 標籤不再被辨識。麻煩的是,廢止之後照抄這段舊教學並不會跳出任何錯誤訊息或警告,瀏覽器只是安靜地不執行轉場,畫面換頁跟平常一樣,什麼事都沒發生。對第一次接觸這個功能的人來說,這種沒有錯誤訊息的失效最容易讓人卡在這一步,以為是自己 CSS 寫錯或瀏覽器不支援,查了半天才發現是教學本身過時。判斷現在該用哪個寫法,最保險的做法是直接核對規格頁面本身,而不是隨便找一篇教學文照抄。

轉場的底層原理是拍照、換頁、再交叉動畫

要理解怎麼自訂轉場效果,得先弄清楚瀏覽器背後實際做了什麼。整個流程可以拆成 4 個步驟:第一步,瀏覽器在畫面即將被換掉前,先拍下舊頁面目前呈現的視覺快照;第二步,卸載舊文件、載入新文件,這段期間畫面的渲染會被暫時抑制,使用者看不到中間過程;第三步,新文件準備好要顯示時,瀏覽器再拍下新頁面的視覺快照;第四步,用一般的 CSS Animation 讓這兩張快照交叉播放,動畫跑完才恢復正常渲染,新頁面正式接手畫面。

View Transitions 換頁時瀏覽器分四步:先拍下舊頁面快照,接著卸載舊文件、載入新文件,再拍下新頁面快照,最後用 CSS Animation 讓兩張快照交叉播放。
轉場的底層原理是拍下前後兩張快照,再用 CSS Animation 補上中間的交叉動畫。

這兩張快照分別對應兩個 CSS 偽元素,::view-transition-old(root) 代表舊畫面的快照,::view-transition-new(root) 代表新畫面的快照。瀏覽器預設幫這兩個偽元素套上一組淡出淡入的動畫,舊快照淡出、新快照淡入,疊在一起就是最常見的交叉淡化效果。這組預設動畫可以直接用 CSS 覆寫,換成滑動、縮放或其他效果都行,寫法就是重新指定這兩個偽元素的 animation-name。

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.4s;
}

@keyframes slide-to-left {
  to {
    transform: translateX(-30px);
    opacity: 0;
  }
}

@keyframes slide-from-right {
  from {
    transform: translateX(30px);
    opacity: 0;
  }
}

::view-transition-old(root) {
  animation-name: slide-to-left;
}

::view-transition-new(root) {
  animation-name: slide-from-right;
}Code language: CSS (css)

上面這段把預設的淡出淡入換成舊頁面往左滑出、新頁面從右滑入的效果,概念上跟原生 App 常見的左右切換頁面很像,而且全程只靠 CSS 完成,不必寫一行 JavaScript 去控制位移或計時。

讓特定元素跨頁對應的 view-transition-name 屬性

前面講的是整頁一起淡出淡入,但很多情境其實只想讓某個元素在兩個頁面之間變形過渡,例如清單頁的縮圖放大變成詳情頁的大圖,或導覽列裡目前作用中的項目跟著移動,而不是整頁畫面一起閃一下。做法是給兩個頁面上概念相同的元素設定相同的 view-transition-name,瀏覽器就會自動幫這個元素做位置與尺寸的補間動畫,業界通常稱作 morph(形變)。

.thumbnail {
  view-transition-name: article-cover;
}

.article-cover-image {
  view-transition-name: article-cover;
}Code language: CSS (css)

以文章清單頁過渡到文章詳情頁為例,只要清單頁的縮圖和詳情頁的主圖都設定了同一個 view-transition-name,讀者點進文章時看到的就不是兩頁各自淡出淡入,而是縮圖本身長大變成主圖,視覺上的連續感明顯更強。

這個屬性有一個限制要記住,同一個轉場過程中,每個名稱在同一時間只能配對一個元素,如果兩個以上的元素同時使用了同一個 view-transition-name,整個轉場會直接失效,退回成普通換頁,連預設的淡出淡入都不會有。跨文件的情境還有另一層複雜,兩個頁面的 DOM 結構通常不完全一樣,清單頁的縮圖跟詳情頁的主圖,標籤、class、所在位置多半都不同,要不要對應、對應哪幾個元素,需要逐一盤點兩邊頁面的結構後再決定,不是每個元素都適合套用。

圖片快照預設會被拉伸,需要靠 object-fit 校正

套用 view-transition-name 做圖片形變時,有一個常見的坑值得專門提醒。轉場快照的本質是一張攤平的點陣圖,如果同一張圖片在舊頁面與新頁面的尺寸比例不一樣,例如清單頁是正方形縮圖、詳情頁是寬螢幕主圖,瀏覽器預設會把舊的那張快照直接拉伸塞進新的框裡,結果圖片看起來被硬拉變形,人臉、Logo 這類有明確形狀的畫面尤其明顯。

修法是替對應的偽元素加上 object-fit: cover,讓快照維持原本的長寬比例、裁切掉多餘的部分,而不是整張硬拉伸。

::view-transition-old(article-cover),
::view-transition-new(article-cover) {
  object-fit: cover;
}Code language: CSS (css)

加上這段之後,轉場過程中圖片會維持比例做局部裁切,看起來就像正常的相片縮放,而不是被壓扁或拉長的破圖。凡是用 view-transition-name 對應圖片元素,而且兩個頁面的顯示比例可能不同,這段 object-fit 幾乎是必加的保險。

瀏覽器支援度呈現同文件成熟、跨文件仍缺一角

目前,View Transitions API 的兩個層級,支援度差了一大截。同文件轉場已經在 2025 年 10 月跨過 Baseline「Newly available」(近期可用)的門檻,代表 Chrome、Edge、Safari 與 Firefox 目前的最新版本全部支援,是四大瀏覽器引擎都到齊的狀態。

跨文件轉場則還停留在 Baseline「Limited availability」(有限可用),意思是還有主流瀏覽器沒跟上。目前 Chrome 與 Edge 從 126 版起支援,Safari 從 18.2 版起支援,但 Firefox 的穩定版目前仍未實作這個 at-rule。比較特別的是,Firefox 對同文件轉場其實支援得不晚,缺的只是跨文件這一半,並不是整個 API 都不支援。

同文件轉場已跨過 Baseline 近期可用,Chrome、Edge、Safari、Firefox 最新版都支援;跨文件轉場仍屬有限可用,Chrome 與 Edge 126 版起、Safari 18.2 版起支援,Firefox 穩定版尚未實作。
同文件轉場四大瀏覽器都到齊,跨文件轉場則還缺 Firefox 這一角,先當漸進增強來用即可。

面對這種一半成熟、一半還沒到齊的狀態,正確的心態是把跨文件轉場當成漸進增強(progressive enhancement)來用,而不是等全部瀏覽器到齊才敢上線。沒有實作這個 at-rule 的瀏覽器,使用者換頁時就是回到原本沒有轉場動畫的樣子,換頁功能本身完全不受影響,不會白屏、不會報錯,也不需要為了這個功能寫任何 polyfill 或去阻擋不支援的瀏覽器存取。

上線前該留意的 3 個實務眉角

demo 展示時看起來很順的轉場效果,搬進正式環境常常會冒出幾個細節問題。以下 3 件事,建議在導入前就先弄清楚,而不是等使用者回報異常才回頭查。

4 秒逾時,頁面載入太慢轉場會靜悄悄消失

跨文件轉場有一個硬性的逾時機制,如果導覽開始之後,新頁面在 4 秒內沒有進入瀏覽器判定為可渲染的狀態,轉場就會直接被取消,頁面照常換過去,但不會跳出任何錯誤訊息或提示。這個計時是從導覽動作發起的那一刻開始算,不是從新頁面的 HTML 開始抵達才起算,所以整個換頁流程,包含伺服器回應、資源下載、瀏覽器解析,都算在這 4 秒之內。

造成逾時最常見的兩個原因是伺服器回應太慢(TTFB 過長),以及會擋住渲染的資源,例如大尺寸的首屏圖片,或是設定成 font-display: block 的字型,在字型下載完成前整個文字都不顯示。從另一個角度看,這個逾時反而是件好事,它變相督促把首屏載入速度處理好,轉場悄悄消失,某種程度上就是網站效能不夠好的一個訊號,值得回頭檢查。

只有同源、使用者主動點擊的導覽才會觸發

navigation: auto 這個值刻意設計得保守,只有同源導覽,而且是使用者實際點擊連結、或按瀏覽器的上一頁/下一頁按鈕時,才會觸發轉場。導覽到別的網域的連結、用 JavaScript 寫的 location.href = ... 這類程式化導覽、還有表單的 POST 提交,都不會套用轉場效果,即使兩個頁面都宣告了 @view-transition 也一樣。

這個限制對導入這個功能的人來說其實是好消息,不必額外寫程式碼去排除某些不該有動畫的操作。例如送出付款表單、送出登入表單這類動作,天生就不會被轉場效果影響,不會有使用者按下付款按鈕、畫面卻先跑一段淡出淡入才跳轉這種讓人分心的體驗。

使用者開啟減少動態效果偏好時要跟著關閉轉場

部分使用者會在作業系統或瀏覽器設定裡,主動開啟減少動態效果(prefers-reduced-motion: reduce)這個偏好設定。對這群使用者來說,畫面轉場動畫不只是不喜歡而已,某些前庭功能敏感的人甚至會因為畫面滑動、縮放的視覺刺激,引發暈眩或噁心等生理不適,這不是體驗上的小事,是無障礙設計必須注意的一環。

做法很簡單,把啟用轉場的那段 CSS 包進 @media (prefers-reduced-motion: no-preference) 條件裡,只在使用者沒有表達偏好的情況下才啟用轉場。

@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}Code language: CSS (css)

這樣寫完全不需要額外寫 JavaScript 去判斷使用者的系統設定,CSS 媒體查詢本身就會自動處理,對開啟減少動態效果的使用者來說,換頁就會維持成最單純直接的跳轉,沒有任何動畫效果。

轉場進行中,用 :active-view-transition 隱藏容易穿幫的元素

除了控制轉場動畫本身,還多了一個專門處理轉場進行中這段期間的 CSS 偽類,:active-view-transition。它只比對根元素(:root),而且只在畫面正處於轉場動畫進行中的這段期間才成立,轉場一結束就自動失效,不需要另外寫程式碼去解除。這個偽類已在 2025 年 10 月跨過 Baseline「Newly available」的門檻,屬於相對新的功能。

典型用途是隱藏轉場過程中容易穿幫的固定元素,像是固定在畫面上的工具列、提示框,或是鍵盤操作留下的 focus ring。這類元素本身不屬於舊畫面或新畫面快照的一部分,但轉場動畫是拿整個畫面去拍照交叉淡出淡入,固定元素疊在上面常常會看起來像殘影或位置跑掉,視覺上很不自然。過去要處理這個問題,只能靠 JavaScript 在轉場的回呼函式裡手動切換某個 class,轉場開始加上、轉場結束拿掉,現在有了這個偽類,直接用宣告式的 CSS 就能寫完。

:root:active-view-transition .toolbar {
  visibility: hidden;
}Code language: CSS (css)

如果需要用程式邏輯判斷目前是否有轉場正在進行,JavaScript 這一側對應的是 Document.activeViewTransition,讀取這個屬性會拿到目前的轉場物件,沒有轉場進行中時則是 null,這個寫法通常用在需要更精細控制的情境,一般的隱藏穿幫元素用途,CSS 偽類就已經夠用。

多頁式 WordPress 網站導入這個功能的兩條路,官方外掛與兩行 CSS

回到這篇一開始提出的問題,多頁式的 WordPress 網站要用上這個功能,完全不需要外掛層級的動畫特效套件,也不必把網站改造成單頁應用。實際能走的路有兩條。

第一條是自己動手,把前面幾節整理出的 @view-transition { navigation: auto; } 直接加進佈景主題的全站樣式表,例如 style.css。因為 WordPress 各種頁面模板,文章頁、列表頁、單頁,本來就共用同一份樣式表,這段 CSS 等於一次宣告就涵蓋了全站每個頁面,不需要逐頁另外設定。

第二條是用官方外掛。WordPress 效能團隊(Performance Team)本身維護了一支名為「View Transitions」的官方外掛,安裝並啟用後,不需要自己寫任何 CSS,「設定」底下的「閱讀」畫面就有開關與動畫時間的設定選項。這支外掛的說明頁明白寫著,它是為了未來可能被納入 WordPress 核心而做的功能實驗,鼓勵使用者回饋意見,裝上它不會帶來額外的安全疑慮。

WordPress 官方 View Transitions 外掛啟用後,會在「設定 → 閱讀」新增檢視畫面轉換效果區段,可挑選轉場動畫效果並設定動畫持續時間,不必自己寫 CSS。
裝上官方 View Transitions 外掛後,「設定 → 閱讀」就能挑轉場動畫效果、設定動畫時間,不用自己寫 CSS。

不管走哪一條路,結果都一樣,沒有支援這個 at-rule 的瀏覽器,使用者看到的就是原本沒有轉場動畫的普通換頁,不會出現任何異常或錯誤畫面,換頁功能本身完全不受影響。

跨文件轉場其實是成本很低的漸進增強,兩三行 CSS,就能讓多頁式網站的換頁動作,從硬邦邦的空白閃過,變成有連續感的轉場。它不會取代單頁應用該解決的複雜前端狀態管理,但對絕大多數靠伺服器渲染、逐頁載入的網站來說,這已經足夠把換頁體感拉到接近原生 App 的水準。等 Firefox 補上這一塊、跨文件轉場真正進到隨處可用的階段,現在先寫好的這段 CSS 也不需要再改一行,提早準備好的網站,到時候只是多了更多使用者能看到這份順暢。

資料來源
  1. View Transitions API Explainer — WICG
  2. View Transitions – WordPress Plugin — WordPress.org