不同文件之間的檢視畫面轉換時,稱為「跨文件檢視轉換」。這在多頁應用程式 (MPA) 中通常都是如此。在 Chrome 126 版中,Chrome 支援跨文件檢視轉場功能。
跨文件檢視轉換功能仰賴的建構模塊和原則,與同一份文件的檢視畫面轉換效果相同,但以下幾點:
- 瀏覽器會針對新舊網頁中含有專屬
view-transition-name
的元素拍攝快照。 - DOM 會在算繪暫停時更新。
- 最後,轉場效果是由 CSS 動畫驅動。
與相同文件檢視模式轉換相比,不同之處在於:在使用跨文件檢視轉場效果時,不必呼叫 document.startViewTransition
就能啟動檢視畫面轉場。相反地,跨文件檢視轉換的觸發事件是從一個頁面到另一個頁面進行導覽,而動作通常是由網站訪客點選連結所執行。
也就是說,沒有 API 可以呼叫,以便在兩份文件之間開始檢視轉換。不過,你必須具備以下兩個條件:
- 兩份文件必須位於相同的來源中。
- 這兩個頁面都必須選擇啟用才能轉換檢視畫面。
本文件稍後會進一步說明這兩項條件。
跨文件檢視轉換功能只適用於相同來源瀏覽
跨文件檢視轉換功能僅適用於相同來源瀏覽。如果兩個參與網頁的來源相同,系統會將導覽視為相同來源。
網頁來源是所用的配置、主機名稱和通訊埠的組合,詳情請參閱web.dev。
舉例來說,從 developer.chrome.com
瀏覽到 developer.chrome.com/blog
時,您可以執行跨文件檢視轉換,因為兩者屬於相同來源。
從 developer.chrome.com
到 www.chrome.com
時,您無法進行轉換,因為這些轉換是跨來源且位於相同網站。
已選擇啟用跨文件檢視轉換功能
如要在兩份文件之間進行跨文件檢視轉換,雙方參與的網頁都必須選擇允許這項功能。方法是使用 CSS 中的 @view-transition
at-rule 設定。
在 @view-transition
at-rule 中,將 navigation
描述元設為 auto
,即可啟用跨文件相同來源瀏覽的檢視轉換。
@view-transition {
navigation: auto;
}
將 navigation
描述元設為 auto
,即表示您選擇允許下列 NavigationType 執行檢視畫面轉換:
traverse
push
或replace
(如果啟動作業不是由使用者透過瀏覽器 UI 機制啟動)。
從 auto
排除的瀏覽包括:使用網址列或書籤進行導覽,以及任何形式的使用者或指令碼重新載入。
如果瀏覽操作所需時間過長 (在 Chrome 中,這會超過 4 秒),系統會使用 TimeoutError
DOMException
略過檢視畫面轉場。
跨文件檢視轉換示範
請觀看以下使用 View 轉換的示範,製作堆疊導覽工具示範。這裡沒有任何對 document.startViewTransition()
的呼叫,而檢視轉換是從一個頁面到另一個頁面時觸發。
自訂跨文件檢視轉場效果
如要自訂跨文件檢視畫面的轉場效果,可以使用一些網路平台功能。
這些功能並非 View Transition API 規格本身的一部分,但應搭配運作方式使用。
pageswap
和 pagereveal
事件
為讓您自訂跨文件檢視畫面的轉場效果,HTML 規格包含您可以使用的兩個新事件:pageswap
和 pagereveal
。
無論觀看轉換是否發生,每個相同來源的跨文件導覽都會觸發這兩個事件。如果兩個網頁之間即將發生檢視畫面轉換,您可以針對這些事件使用 viewTransition
屬性存取 ViewTransition
物件。
pageswap
事件會在網頁最後一個影格轉譯前觸發。您可以在產生舊快照前,先在目標頁面進行最後幾分鐘變更。pagereveal
事件在初始化或重新啟用後,會在第一個顯示商機之前。讓您在系統拍攝新快照前,可以先自訂新頁面。
舉例來說,您可以利用這些事件快速設定或變更部分 view-transition-name
值,或是從 sessionStorage
寫入資料,藉此自訂檢視轉換「之前」,藉此自訂檢視轉換。
let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
if (event.target.tagName.toLowerCase() === 'a') return;
lastClickX = event.clientX;
lastClickY = event.clientY;
});
// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
if (event.viewTransition && lastClick) {
sessionStorage.setItem('lastClickX', lastClickX);
sessionStorage.setItem('lastClickY', lastClickY);
}
});
// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
if (event.viewTransition) {
lastClickX = sessionStorage.getItem('lastClickX');
lastClickY = sessionStorage.getItem('lastClickY');
}
});
如有需要,您可以同時在這兩個事件中都略過轉換程序。
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
pageswap
和 pagereveal
中的 ViewTransition
物件是兩個不同的物件。他們也會以不同方式處理各種承諾:
pageswap
:文件隱藏後,系統會略過舊的ViewTransition
物件。發生這種情況時,viewTransition.ready
會拒絕,viewTransition.finished
則會解析。pagereveal
:現在的updateCallBack
承諾已經解決。您可以使用viewTransition.ready
和viewTransition.finished
承諾。
導航啟用資訊
在 pageswap
和 pagereveal
事件中,你也可以根據新舊網頁的網址採取行動。
例如,在 MPA 堆疊導覽器中要使用的動畫類型取決於導覽路徑:
- 從總覽頁面前往詳細資料頁面時,新內容必須從右向左滑動。
- 從詳細資料頁面前往總覽頁面時,舊內容需要從左向右滑動。
為此,您必須取得導覽相關資訊 (如果是 pageswap
,則會即將發生,如果 pagereveal
剛發生)。
為此,瀏覽器現在可以公開含有相同來源導覽資訊的 NavigationActivation
物件。這個物件會公開使用的導覽類型、目前和最終目的地歷史記錄項目 (如 navigation.entries()
透過 Navigation API 取得) 所示。
在已啟用的頁面中,您可以透過 navigation.activation
存取這個物件。在 pageswap
事件中,您可以透過 e.activation
進行存取。
請查看這份設定檔示範,瞭解如何使用 pageswap
和 pagereveal
事件中的 NavigationActivation
資訊,針對需要參與檢視轉換的元素設定 view-transition-name
值。
這樣一來,您就不需要預先使用 view-transition-name
裝飾清單中的每個項目。相反地,使用 JavaScript 時只會在有需要的元素上即時執行此作業。
程式碼如下:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove view-transition-names after snapshots have been taken
// (this to deal with BFCache)
await e.viewTransition.finished;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove names after snapshots have been taken
// so that we're ready for the next navigation
await e.viewTransition.ready;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
程式碼也會在檢視畫面轉換執行後移除 view-transition-name
值,自行清理程式碼。如此一來,頁面即可提供連續的瀏覽功能,也能處理過去的周遊記錄。
為協助解決這個問題,請使用這個公用程式函式來暫時設定 view-transition-name
。
const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
for (const [$el, name] of entries) {
$el.style.viewTransitionName = name;
}
await vtPromise;
for (const [$el, name] of entries) {
$el.style.viewTransitionName = '';
}
}
舊版程式碼現在可簡化,如下所示:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
// Clean up after the page got replaced
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.finished);
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
// Clean up after the snapshots have been taken
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.ready);
}
}
});
等待內容透過轉譯封鎖功能載入
在某些情況下,您可能會想先暫停網頁首次轉譯,直到特定元素出現在新 DOM 中。這樣做可以避免閃爍,並且確保您動畫的狀態穩定。
在 <head>
中,使用以下中繼標記定義一或多個必須在網頁首次轉譯前顯示的元素 ID。
<link rel="expect" blocking="render" href="#section1">
這個中繼標記表示該元素應該出現在 DOM 中,而不是應該載入內容。舉例來說,如果是圖片,只需要在 DOM 樹狀結構中指定 id
的 <img>
標記,就足以讓條件評估為 true。仍在載入圖片本身。
全面啟用轉譯封鎖功能前,請注意漸進式轉譯是網頁的基本要素,因此選擇封鎖顯示功能時請小心謹慎。需要評估封鎖算繪的影響。根據預設,除非您可以主動評估及評估網站體驗核心指標受到的影響,否則請避免使用 blocking=render
。
查看跨文件檢視轉場效果的轉換類型
跨文件檢視轉場效果也支援檢視畫面轉換類型,可用來自訂動畫和擷取的元素。
舉例來說,在分頁中前往下一頁或返回上一頁時,建議您根據前往的頁面是較高頁面,或是序列下的網頁,使用不同的動畫。
如要預先設定這些類型,請在 @view-transition
at-rule 中新增類型:
@view-transition {
navigation: auto;
types: slide, forwards;
}
如要即時設定類型,請使用 pageswap
和 pagereveal
事件控管 e.viewTransition.types
的值。
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
e.viewTransition.types.add(transitionType);
}
});
類型不會自動從舊頁面的 ViewTransition
物件轉移至新頁面的 ViewTransition
物件。您必須決定至少在新頁面上使用的類型,動畫才能正常運作。
如要回應這些類型,請使用:active-view-transition-type()
虛擬類別選取器,方法與使用相同文件檢視轉場效果相同
/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
:root {
view-transition-name: none;
}
article {
view-transition-name: content;
}
.pagination {
view-transition-name: pagination;
}
}
/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-left;
}
&::view-transition-new(content) {
animation-name: slide-in-from-right;
}
}
/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-right;
}
&::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
&::view-transition-old(root) {
animation-name: fade-out, scale-down;
}
&::view-transition-new(root) {
animation-delay: 0.25s;
animation-name: fade-in, scale-up;
}
}
由於類型僅適用於 Active View 轉場效果,因此類型會在檢視畫面轉換作業完成後自動清除。因此,類型適用於 BFCache 等功能。
操作示範
在以下的分頁示範中,網頁內容會根據您前往的頁碼向前或向後滑動。
要使用的轉換類型取決於網址間的 和 網址,在 pagereveal
和 pageswap
事件中。
const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
const currentURL = new URL(fromNavigationEntry.url);
const destinationURL = new URL(toNavigationEntry.url);
const currentPathname = currentURL.pathname;
const destinationPathname = destinationURL.pathname;
if (currentPathname === destinationPathname) {
return "reload";
} else {
const currentPageIndex = extractPageIndexFromPath(currentPathname);
const destinationPageIndex = extractPageIndexFromPath(destinationPathname);
if (currentPageIndex > destinationPageIndex) {
return 'backwards';
}
if (currentPageIndex < destinationPageIndex) {
return 'forwards';
}
return 'unknown';
}
};
意見回饋:
我們隨時歡迎開發人員提供寶貴意見。如要分享意見,請在 GitHub 上向「CSS Working Group」提交問題,並附上建議和問題。請在 [css-view-transitions]
前方加上你的問題。
如果您遇到錯誤,請改為回報 Chromium 錯誤。