這篇文我們來看一下怎麼使用 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;
}
從上面的 CSS 可以看到,我們先定義 header
的 view-transition-name
為 header-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>
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 過去自己試試看。
總結起來,使用上不外乎就是兩個重點:
- 定義
view-transition
。
@view-transition {
navigation: auto;
}
/* 其他自定義轉場效果 */
- 在 SPA 情境中,畫面改變時由於不會請求新的頁面,需使用
document.startViewTransition()
觸發。
以上就是使用 View Transition API 我整理下來的使用方式,若內文有任何錯誤也歡迎指出,謝謝!
References: