使用 View Transitions API 實現換頁轉場(以 MPA, Vue, React 為例)

Eason Lin
12 min readNov 23, 2024

--

Photo by Yvette de Wit on Unsplash

這篇文我們來看一下怎麼使用 View Transition API。在開始前,讓我們先快速看個 MPA 的例子體驗一下

什麼是 View Transitions API?

如上所見,View Transitions API 提供了一個能讓不同視圖(View)在切換時產生轉場(Transitions)效果的機制。這是一個約 2023 頁開始被瀏覽器實作,能夠實現流暢的頁面轉場,但目前仍未完全普及於主流瀏覽器的功能。它可以減少使用者體感上的等待時間,減少使用者離開網站的可能性。而其強大之處在於不僅僅是 SPA、連在 MPA 上都可以實現

使用 View Transition API 的具體流程如下:

MPA

⭐️ 觸發 View Transition
在 MPA 的情境中,View Transition 透過對頁面設定 navigation 的 CSS 來觸發:

@view-transition {
navigation: auto;
}

只要在頁面共用的 CSS 加入上述這三行,在切換頁面時就可以看到預設的轉場,也就是淡出淡入的效果。請注意,新舊頁面須為同源(Same Origin),否則不會生效

如果在本地碰到「加上了這三行,依然沒有作用的情況」,可以試試看在開啟 Devtool 的狀況下操作。我嘗試在網路上搜尋為何本地必須在開啟 Devtool 才會生效,目前未查到相關資料。不過 ChatGPT 給了以下回覆:

View Transitions API 在處理過渡動畫時,可能因為頁面加載過快或 JavaScript 的執行時序問題,導致動畫未被觸發。但當開啟 DevTools 時,頁面會強制重排或重繪,這無意間修復了這個問題。」

由於暫時未找到相關文章證實,目前對此我持保留態度。另一個作法則是將其推送到 Github Page 或相關服務,應該也會能正常運作。

⭐️ 定義 view-transition-name, ::view-transition-group, ::view-transition-old 及 ::view-transition-new

view-transition-name 是一個 CSS 屬性,我將它理解為「讓瀏覽器辨識使用它的元素在轉場時要使用哪種效果」。其可以搭配 ::view-transition-group , ::view-transition-old , ::view-transition-new 一起使用。例如:

@keyframes move-out {
from {
transform: translateY(0%);
}

to {
transform: translateY(-100%);
}
}

@keyframes move-in {
from {
transform: translateY(100%);
}

to {
transform: translateY(0%);
}
}

header {
view-transition-name: header-transition;
}

::view-transition-group(header-transition) {
animation-duration: 0.5s;
}

::view-transition-old(header-transition) {
animation-name: move-out;
}

::view-transition-new(header-transition) {
animation-name: move-in;
}
可以發現 header 採用了上移,其餘元素則採用淡入淡出的效果

從上面的 CSS 可以看到,我們先定義 headerview-transition-nameheader-transition

::view-transition-group 我將其理解為「::view-transition-old::view-transition-new 的父層」,所以可以在這邊撰寫舊頁與新頁的共用效果。

::view-transition-old::view-transition-new 則代表了舊頁離場、新頁進場的效果。三者皆採用 (header-transition) ,表示被套用於 header-transition 這個轉場。

若要整頁套用相同的轉場效果,可以使用 root

::view-transition-group(root) {
animation-duration: 0.5s;
}

::view-transition-old(root) {
animation-name: move-out;
}

::view-transition-new(root) {
animation-name: move-in;
}

SPA

⭐️ 觸發 View Transition
與 MPA 最主要的不同,在 SPA 的情境中,由於 route 改變時不會請求新的頁面,而是運用 History API 配合對應框架的邏輯去更新畫面來做到更新畫面,我們需要透過 View Transition API 告訴瀏覽器「我要換頁了」,View Transition API 透過將變更 DOM 更新的函式作為回呼,傳遞給 document.startViewTransition() 方法來觸發:

document.startViewTransition(updateCallback)

以 Vue 為例,document.startViewTransition 可以寫在導航守衛:

router.beforeEach((before, after, next) => {
document.startViewTransition(() => {
next()
})
})

以 React 為例,可以自定義轉場組件:

import { useNavigate } from 'react-router'

function CustomLink({ to, children }) {
const linkStyle = {
color: 'blue',
cursor: 'pointer',
textDecoration: 'underline',
border: 'none',
background: 'none',
}
const navigateTo = useNavigate()
const navigate = (to) => {
document.startViewTransition(() => {
navigateTo(to)
})
}
return (
<button className="custom-link" style={linkStyle} onClick={() => navigate(to)}>
{children}
</button>
);
}

export default CustomLink

// 實際應用範例
// <CustomLink to="/about">About</CustomLink>

SPA 的實例畫面可以參考此

document.startViewTransition 這個函式會回傳一個 ViewTransition 的物件,裡面包含 updateCallbackDone , ready , finished 等三個屬性,值均為 Promise。

⭐️ 定義 view-transition-name, ::view-transition-group, ::view-transition-old 及 ::view-transition-new

CSS 範例和 MPA 一樣。

@keyframes move-out {
from {
transform: translateY(0%);
}

to {
transform: translateY(-100%);
}
}

@keyframes move-in {
from {
transform: translateY(100%);
}

to {
transform: translateY(0%);
}
}

@view-transition {
navigation: auto;
}

header {
view-transition-name: header-transition;
}

::view-transition-group(header-transition) {
animation-duration: 0.5s;
}

::view-transition-old(header-transition) {
animation-name: move-out;
}

::view-transition-new(header-transition) {
animation-name: move-in;
}

⭐️ 在目前頁面中,API 會擷取有宣告 view-transition-name 元素的快照(snapshot)。

這裡和 MPA 情境一樣。

⭐️ View 發生變化時
在 SPA 的情境中,傳遞到 startViewTransition() 的回呼會被呼叫,表示 DOM 發生了改變。當回呼成功運行時,ViewTransition.updateCallbackDone Promise 狀態會變為 fullfilled

⭐️ API 會取得新頁面的快照,此時已經準備進行轉場效果,ViewTransition.ready 的 Promise 狀態會變為 fulfilled。

⭐️ 舊頁面開始進行離開的轉場、新頁面開始進行進入的轉場。預設情況下,舊頁面的快照會進行淡出(透明度 1 -> 0),新頁面的快照會進行淡入(透明度 0 -> 1)。

⭐️ 轉場完成後,ViewTransition.finished 的 Promise 狀態會變成 fulfilled。

若需要在 ViewTransition 的過程進行特定操作,則可以在這三個階段 Promise fullfilled 時進行。例如:

router.beforeEach((before, after, next) => {
const viewTransition = document.startViewTransition(() => {
next()
})
viewTransition.ready.then(() => {
console.log('ready resolved')
})
viewTransition.updateCallbackDone.then(() => {
console.log('updateCallbackDone resolved')
})
viewTransition.finished.then(() => {
console.log('finished resolved')
})
})

實際渲染效果如下:

若要參考原始碼,我將範例放在 Github 上了,歡迎 clone 或 fork 過去自己試試看。

--

--

Eason Lin
Eason Lin

Written by Eason Lin

Frontend Web Developer | Books

No responses yet