主題: Web platform

View Transitions 不會把 MPA 變 SPA,但會讓它更順

View Transition API 已能處理同文件狀態與跨頁導覽。這篇整理最小用法、瀏覽器退路、shared element 命名與 reduced motion。

動態迷因(展開/收合)
只想要淡入淡出,卻先造了一整套 SPA 機械。 · 來源:GIPHY

以前想讓文章列表的縮圖平順移到內頁 hero,常見做法是先把網站變成 SPA,再接 router transition、狀態管理與一段專門等動畫結束的 JavaScript。

現在跨頁效果可以從兩行 CSS 開始:

@view-transition {
  navigation: auto;
}

難怪最近的 daily.dev View Transitions recipesReddit 的現代 Web API 討論 都很熱鬧。這個 API 終於從有趣 demo,走到值得放進一般產品的階段。

不過先拆掉一個誤會:View Transitions 只負責視覺狀態的交接,不負責 routing、資料取得、cache、optimistic update 或離線狀態。 它能讓 MPA 看起來更連續,卻不會把 MPA 變成 SPA。

這反而是它最好的地方。若網站本來不需要 client router,就不用為了一段轉場把整套架構搬進瀏覽器。

其實是兩種轉場,共用同一套概念

MDN 的 View Transition API涵蓋兩條路徑:

情境 觸發方式 適合工作
Same-document document.startViewTransition(update) 排序、分頁、切換月份、SPA route
Cross-document @view-transition { navigation: auto; } 同源 MPA 的頁面導覽

Same-document 轉場會先擷取舊狀態,執行 callback 裡的 DOM 更新,再讓舊畫面淡出、新畫面淡入。這組核心功能在 Firefox 144 上線後,已於 2025 年 10 月成為 Baseline Newly available

Cross-document 不需要 JavaScript。瀏覽器在舊文件與新文件之間處理 snapshot 與 navigation,但有三個前提:

  1. 兩頁必須同源。
  2. 起點與終點頁面都要用 @view-transition opt in。
  3. 導覽不能經過 cross-origin redirect。

這些條件來自 CSS View Transitions Level 2 的跨文件模型。登入服務、第三方付款或外部連結通常不符合,就讓它們維持正常導覽。

最安全的 MPA 起點:只在允許動態效果時 opt in

@view-transition 目前在 MDN 仍標示為 Limited availability。好消息是,它很適合 progressive enhancement:不支援的瀏覽器忽略規則,連結照常開啟,新頁面照常載入。

我會從更保守的版本開始:

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

沒有動畫 library,沒有 feature-detection script,也沒有 fallback router。支援且未要求減少動態效果的瀏覽器得到預設 cross-fade;其他使用者得到原本就可靠的頁面導覽。

這個失敗模式很乾淨。如果頁面載入太慢,Chrome 甚至會在等待超過約四秒後跳過轉場,而不是讓 snapshot 永遠蓋在畫面上。轉場是加分,不該成為進入新頁面的前置條件。

Shared element 的重點不是動畫,而是 identity

整頁淡入淡出只需要 opt in。要讓列表縮圖移到文章 hero,兩邊元素必須共享同一個 view-transition-name

/* 列表頁與文章頁各自只能有一個符合的元素 */
[data-transition-cover="42"] {
  view-transition-name: cover-42;
}

列表頁的第 42 張縮圖與內頁 hero 都使用 cover-42,瀏覽器便能把舊、新 snapshot 配成一組。名字代表「這是同一個視覺物件」,不是「請套用某個動畫」。

這裡最容易踩兩個坑:

  • 同一個畫面不能有兩個可見元素使用相同名稱。列表中每張卡片都寫 view-transition-name: cover,不會自動猜出點了哪張。
  • view-transition-name: match-element 適合同文件內替大量元素自動命名,但不同文件中的 DOM 元素沒有相同 identity,不能靠它完成跨頁 shared element。

所以真正需要的通常不是 animation helper,而是一個穩定 ID。由 template 用文章 slug 或資料 ID 產生 cover-<id> 已經足夠;不必建立動畫 registry。

SPA 也不必把更新邏輯交給 API

Same-document 的最低風險寫法,是先保留一個不含動畫也能正確執行的更新函式:

function updateResults() {
  results.replaceChildren(...nextItems);
}

if (document.startViewTransition) {
  document.startViewTransition(updateResults);
} else {
  updateResults();
}

Callback 不是讓 View Transition 接管應用程式狀態。它只是告訴瀏覽器:「舊狀態已經可以截圖,現在執行真正的 DOM 更新。」

這個界線值得守住。資料取得應先完成,錯誤處理仍由原本流程負責;動畫失敗時,資料與 DOM 更新仍必須成功。若把 fetch、router side effects 與動畫時間全部塞進 callback,原本簡單的視覺增強很快又會變成新的控制中心。

它不能替代 Router 的事

View Transitions 可以讓 server-rendered 頁面在 navigation 時交接得更自然,但下面需求仍是 architecture,不是 animation:

  • App shell 必須長期保留
  • 切頁後要保存複雜 client state
  • 需要 optimistic UI 或離線操作
  • 只更新部分資料,不重新取得完整文件
  • Route loader、prefetch 與 cache 是產品效能核心

有這些需求,client router 仍可能是正確選擇。沒有這些需求,只想避免頁面切換太突兀,就先用平台原生的跨文件轉場。

我會這樣選:

需求 做法
月曆前後月份、列表重新排序 Same-document transition
文章列表進入文章內頁 Cross-document transition
Auth、付款、外部網站 普通 navigation
Optimistic dashboard、離線 app Router/SPA architecture
Button hover、展開一個區塊 普通 CSS transition

最後一列很重要。不是每個位移都需要 snapshot 與 pseudo-element tree;元素自己還活在同一個位置時,CSS transition 通常更直接。

動態迷因(展開/收合)
如果一個淡入淡出開始要求 router、global store 與 lifecycle manager:真的沒必要。 · 來源:GIPHY

動畫要解釋方向,不要只證明技術存在

View Transition 預設是 cross-fade,已經比「每頁都從右邊飛進來」安全。真正值得自訂的動畫,通常能幫使用者理解空間或因果:

  • 點縮圖後,圖片成為內頁 hero
  • 月曆往下一個月時向左移,回上一個月時向右移
  • 排序後,項目移到新位置而不是瞬間消失再出現

相反地,整頁旋轉、縮放、3D flip 雖然適合 demo,未必適合每天操作的產品。動畫時間越長,越像在收取使用者的注意力稅。

也不要把 prefers-reduced-motion 理解成「動畫播放快一點」。MDN 的 reduced motion 指南是用較溫和效果取代可能引發前庭不適的縮放動態。最簡單的產品預設,是只在 no-preference 下啟用跨頁轉場;如果保留 reduced mode,就用短 cross-fade,別用大幅移動與縮放。

上線前的五個檢查

  1. 關掉支援再測一次。 所有操作仍要正常完成。
  2. 啟用系統 Reduce Motion。 頁面不能偷偷保留大幅 slide 或 scale。
  3. 測 Back/Forward。 BFCache 恢復時,暫時加入的 transition name 要清掉。
  4. 測慢頁面與失敗回應。 動畫不得遮住錯誤或阻擋 navigation。
  5. 測 responsive breakpoint。 同名元素在任何寬度都只能出現一個。

這五項都通過後,再討論 easing 與 duration。因為 View Transitions 的最佳 fallback 不是另一套動畫,而是本來就能用的網站。

結論:把轉場當 CSS,不要當架構

View Transition API 最值得注意的進展,不是它能做多少華麗效果,而是正常網站不必先變成 SPA 才能擁有連續的頁面感。

Same-document 核心功能已跨主要瀏覽器;cross-document 即使尚未完整 Baseline,也能在不支援時安靜退回普通導覽。這正是適合現在採用的 progressive enhancement。

我的預設很簡單:先讓 navigation 與 DOM update 在零動畫時完全正確,再用 View Transitions 補上能解釋位置、方向或因果的動態。

若一段轉場開始要求自製 router、global animation store 或複雜 lifecycle manager,問題多半不是 API 不夠強,而是視覺增強已經越權成為架構。


外部參考連結