當在單一文件上執行檢視畫面轉換時,即稱為「相同文件檢視轉換」。這通常是單頁應用程式 (SPA) 的情況,因為 SPA 會使用 JavaScript 更新 DOM。自 Chrome 111 版起,Chrome 就支援相同文件檢視畫面的轉場效果。
如要觸發同文件檢視模式的轉換,請呼叫 document.startViewTransition
:
function handleClick(e) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow();
return;
}
// With a View Transition:
document.startViewTransition(() => updateTheDOMSomehow());
}
在叫用時,瀏覽器會自動擷取所有宣告 view-transition-name
CSS 屬性的元素快照。
接著,它會執行傳入的回呼,更新 DOM,然後擷取新狀態的快照。
這些快照會排列在偽元素樹狀結構中,並使用 CSS 動畫功能製作動畫。新舊狀態的快照組合也能從舊位置和大小順利轉換到新位置,同時內容則交錯淡出。如有需要,您可以使用 CSS 自訂動畫。
預設轉場效果:交叉淡出
預設的檢視畫面轉場效果為交錯淡出,因此是這個 API 的很好簡介:
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// With a transition:
document.startViewTransition(() => updateTheDOMSomehow(data));
}
其中 updateTheDOMSomehow
會將 DOM 變更為新的狀態。您可以視需要進行這項操作。例如新增或移除元素、變更類別名稱或樣式。
這樣一來,頁面就會交錯淡出淡入:
好吧,交叉淡出效果不怎麼吸引人。好消息是,您可以自訂轉場效果,但首先必須瞭解這項基本交叉淡出效果的運作方式。
轉場效果的運作方式
接著更新先前的程式碼範例。
document.startViewTransition(() => updateTheDOMSomehow(data));
呼叫 .startViewTransition()
時,API 會擷取網頁目前的狀態。包括擷取快照。
完成後,系統會呼叫傳遞至 .startViewTransition()
的回呼。這就是 DOM 變更的地方。接著,API 會擷取網頁的新狀態。
擷取新狀態後,API 會建構類似以下的偽元素樹狀結構:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
::view-transition
會顯示在疊加層,覆蓋頁面上的所有其他內容。如果您想為轉場效果設定背景顏色,這項功能就能派上用場。
::view-transition-old(root)
是舊檢視畫面的螢幕截圖,::view-transition-new(root)
則是新檢視畫面的即時表示法。兩者都顯示為 CSS「取代內容」(例如 <img>
)。
舊檢視畫面以 opacity: 1
為 opacity: 0
的動畫效果,新檢視畫面則會從 opacity: 0
到 opacity: 1
的動畫效果,產生交叉淡出。
所有動畫都是使用 CSS 動畫執行,因此可以使用 CSS 自訂動畫。
自訂轉場效果
所有檢視畫面轉場虛擬元素都可透過 CSS 指定,而且動畫是使用 CSS 定義,因此您可以使用現有的 CSS 動畫屬性修改這些元素。例如:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
經過這項變更後,淡出效果現在會變得非常緩慢:
好的,這樣還不夠精彩。以下程式碼會實作 Material Design 的共用軸轉場效果:
@keyframes fade-in {
from { opacity: 0; }
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(30px); }
}
@keyframes slide-to-left {
to { transform: translateX(-30px); }
}
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
結果如下:
,瞭解如何調查及移除這項存取權。轉換多個元素
在前次示範中,整個網頁都涉及到共用軸轉換。這項設定適用於大部分的網頁,但似乎不適合標題,因為標題會滑出後又滑回。
為避免這種情況,您可以從頁面其餘部分中擷取頁首,以便分別為其製作動畫。方法是將 view-transition-name
指派給元素。
.main-header {
view-transition-name: main-header;
}
view-transition-name
可以是任何值,但 none
除外,表示沒有轉換名稱。可用於「唯一」識別轉場效果的元素。
結果如下:
標頭現在會保持原位並進行交叉淡出。
這個 CSS 宣告會導致虛擬元素樹狀結構變更:
::view-transition
├─ ::view-transition-group(root)
│ └─ ::view-transition-image-pair(root)
│ ├─ ::view-transition-old(root)
│ └─ ::view-transition-new(root)
└─ ::view-transition-group(main-header)
└─ ::view-transition-image-pair(main-header)
├─ ::view-transition-old(main-header)
└─ ::view-transition-new(main-header)
目前有兩個轉換群組。一個用於標題,另一個用於其餘內容。且可透過 CSS 單獨指定,且可以採用不同的轉場效果。不過在本範例中,main-header
原本使用預設轉場效果,也就是交叉淡出。
好的,預設轉場效果不只是交叉淡出,::view-transition-group
也會轉場效果:
- 位置和轉換 (使用
transform
) - 寬度
- 高度
直到目前為止,這個標題都沒關係,因為標題與 DOM 每一面的大小和位置都相同。不過,您也可以擷取標題中的文字:
.main-header-text {
view-transition-name: main-header-text;
width: fit-content;
}
使用 fit-content
可讓元素的大小與文字相同,而不會延伸至剩餘的寬度。否則,返回箭頭會縮小標題文字元素的大小,而不會在兩個頁面中顯示相同大小。
因此,我們現在有三個部分可供操作:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
但再次套用預設值即可:
現在標題文字會滑動至適當位置,為返回按鈕騰出空間。
使用 view-transition-class
以相同方式為多個疑似元素設定動畫
瀏覽器支援
假設你有兩個資訊卡的檢視畫面轉換,但同時也是頁面標題。如要為標題以外的所有資訊卡製作動畫,您必須編寫選取器,指定每張資訊卡。
h1 {
view-transition-name: title;
}
::view-transition-group(title) {
animation-timing-function: ease-in-out;
}
#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
…
#card20 { view-transition-name: card20; }
::view-transition-group(card1),
::view-transition-group(card2),
::view-transition-group(card3),
::view-transition-group(card4),
…
::view-transition-group(card20) {
animation-timing-function: var(--bounce);
}
上面有 20 個元素嗎?也就是說,您需要編寫 20 個選取器。新增元素?接著,您也需要擴充套用動畫樣式的 selector。不完全可擴充。
view-transition-class
可用於檢視畫面轉換虛擬元素,以套用相同的樣式規則。
#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
#card5 { view-transition-name: card5; }
…
#card20 { view-transition-name: card20; }
#cards-wrapper > div {
view-transition-class: card;
}
html::view-transition-group(.card) {
animation-timing-function: var(--bounce);
}
下列資訊卡範例採用先前的 CSS 程式碼片段。所有卡片 (包括新增的卡片) 都會套用與單一選取器相同的顯示時間:html::view-transition-group(.card)
。
轉場偵錯
由於檢視畫面轉場效果是建立在 CSS 動畫之上,因此 Chrome 開發人員工具中的「Animations」面板非常適合用於偵錯轉場效果。
使用「Animations」面板,您可以暫停下一個動畫,然後透過拖曳的方式瀏覽動畫。在這個期間,您可以在「Elements」面板中找到轉場虛擬元素。
轉場效果的元素不一定要是同一個 DOM 元素
到目前為止,我們已使用 view-transition-name
為標頭和標頭中的文字建立個別的轉場元素。在概念上,DOM 變更前後的元素是相同的,但您可以建立不符合此情況的轉場效果。
舉例來說,您可以為主要影片嵌入 view-transition-name
:
.full-embed {
view-transition-name: full-embed;
}
然後,在使用者按下縮圖時,可以按照轉場效果提供相同的 view-transition-name
:
thumbnail.onclick = async () => {
thumbnail.style.viewTransitionName = 'full-embed';
document.startViewTransition(() => {
thumbnail.style.viewTransitionName = '';
updateTheDOMSomehow();
});
};
結果如下:
縮圖現已轉換成主要圖片。雖然它們在概念上 (和字面上) 是不同的元素,但轉換 API 會將它們視為相同,因為它們共用相同的 view-transition-name
。
這項轉場效果的實際程式碼比上述範例複雜一些,因為它也處理返回縮圖頁面的轉場效果。如需完整實作方式,請參閱原始碼。
自訂進入和離開轉場效果
請參考以下範例:
側欄是轉場效果的一部分:
.sidebar {
view-transition-name: sidebar;
}
不過,與上例中的頁首不同,側欄並非在所有頁面上顯示。如果兩個狀態都有側欄,轉場虛擬元素會如下所示:
::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
└─ ::view-transition-image-pair(sidebar)
├─ ::view-transition-old(sidebar)
└─ ::view-transition-new(sidebar)
不過,如果側欄只位於新頁面,就不會出現 ::view-transition-old(sidebar)
擬似元素。由於側欄沒有「舊」圖片,因此圖片組合只會顯示 ::view-transition-new(sidebar)
。同樣地,如果側欄只出現在舊版網頁上,圖片組合就只會有 ::view-transition-old(sidebar)
。
在先前的示範中,側欄的轉場效果會因進入、離開或同時處於這兩種狀態而有所不同。從右側滑入並淡出,即可進入另一個模式,滑向右邊則淡出,之後就會離開。
如要建立特定的進入和退出轉場效果,您可以使用 :only-child
擬似類別,在圖片組中找出唯一子項,然後指定舊或新擬似元素:
/* Entry transition */
::view-transition-new(sidebar):only-child {
animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Exit transition */
::view-transition-old(sidebar):only-child {
animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}
在這種情況下,由於預設值已達到完美狀態,因此在兩種狀態中顯示側欄時,沒有特定的轉場效果。
非同步 DOM 更新,以及等待內容
傳遞至 .startViewTransition()
的回呼可以傳回承諾,進而允許非同步 DOM 更新,並等待重要內容準備就緒。
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
承諾完成後,系統才會開始轉換。在這段期間內,頁面將處於凍結狀態,因此延遲時間應降至最低。具體來說,網路擷取作業必須在呼叫 .startViewTransition()
之前完成,同時網頁仍具備完整互動功能,而非做為 .startViewTransition()
回呼的一部分。
如果您決定等待圖片或字型準備就緒,請務必使用積極的逾時值:
const wait = ms => new Promise(r => setTimeout(r, ms));
document.startViewTransition(async () => {
updateTheDOMSomehow();
// Pause for up to 100ms for fonts to be ready:
await Promise.race([document.fonts.ready, wait(100)]);
});
不過,在某些情況下,建議您完全避免延遲,並使用現有的內容。
充分運用現有內容
如果縮圖轉換為較大的圖片:
預設的轉場效果為交錯淡出,也就是說,縮圖在尚未載入的完整圖片時可能會交替淡出。
處理這個問題的其中一種方法,就是等到整個圖片載入完成後再開始轉場。理想情況下,應在呼叫 .startViewTransition()
之前完成這項操作,以便讓網頁保持互動性,並顯示旋轉圖示,向使用者顯示內容正在載入。但在這種情況下還有更好的做法:
::view-transition-old(full-embed),
::view-transition-new(full-embed) {
/* Prevent the default animation,
so both views remain opacity:1 throughout the transition */
animation: none;
/* Use normal blending,
so the new view sits on top and obscures the old view */
mix-blend-mode: normal;
}
縮圖現在不會淡出,而是會顯示在完整圖片下方。換句話說,如果新的檢視畫面尚未載入,系統就會在整個轉場期間持續顯示縮圖。這表示轉場效果可以立即開始,而完整圖片則可在其自身時間內載入。
如果新檢視畫面具有透明度,這項最佳化就無法運作,但在本例中,我們知道該檢視畫面沒有透明度,因此可以進行這項最佳化。
處理顯示比例變更
方便起見,到目前為止,所有轉場效果都會套用至長寬比相同的元素,但這並非一成不變的情況。如果縮圖為 1:1,而主要圖片為 16:9 時,該怎麼辦?
在預設轉場效果中,群組會從之前的大小變成動畫後的大小。新舊檢視畫面的寬度為群組寬度的 100%,且會自動高度,因此無論群組大小為何,其顯示比例都會保持不變。
這是一個不錯的預設設定,但並不在本例中達到預期效果。舉例來說:
::view-transition-old(full-embed),
::view-transition-new(full-embed) {
/* Prevent the default animation,
so both views remain opacity:1 throughout the transition */
animation: none;
/* Use normal blending,
so the new view sits on top and obscures the old view */
mix-blend-mode: normal;
/* Make the height the same as the group,
meaning the view size might not match its aspect-ratio. */
height: 100%;
/* Clip any overflow of the view */
overflow: clip;
}
/* The old view is the thumbnail */
::view-transition-old(full-embed) {
/* Maintain the aspect ratio of the view,
by shrinking it to fit within the bounds of the element */
object-fit: contain;
}
/* The new view is the full image */
::view-transition-new(full-embed) {
/* Maintain the aspect ratio of the view,
by growing it to cover the bounds of the element */
object-fit: cover;
}
也就是說,當寬度擴大時,縮圖會停留在元素中央,但完整圖片會從 1:1 轉換為 16:9 時「取消裁剪」。
詳情請參閱「檢視區塊轉場:處理顯示比例變更」一文
使用媒體查詢變更不同裝置狀態的轉場效果
建議您在行動裝置和電腦上使用不同的轉場效果。如本範例,在行動裝置上是從側邊呈現整張投影片,但在電腦螢幕上則需要更細緻的轉場效果:
您可以使用一般媒體查詢來達成這項目標:
/* Transitions for mobile */
::view-transition-old(root) {
animation: 300ms ease-out both full-slide-to-left;
}
::view-transition-new(root) {
animation: 300ms ease-out both full-slide-from-right;
}
@media (min-width: 500px) {
/* Overrides for larger displays.
This is the shared axis transition from earlier in the article. */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
}
您可能也需要根據相符的媒體查詢,變更要指派 view-transition-name
的元素。
回應「減少動態」效果偏好
使用者可以透過作業系統表示偏好減少動畫效果,且偏好設定會在 CSS 中公開。
您可以選擇為下列使用者停用所有轉換:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
不過,設定為「減少動畫」並不代表使用者想要「沒有動畫」。您可以選擇更精緻的動畫,而非上述程式碼片段,但仍要能表達元素之間的關係和資料流程。
使用檢視畫面轉換類型處理多個檢視畫面轉換樣式
瀏覽器支援
有時在轉換到另一個檢視畫面時,應有特別量身打造的轉場效果。舉例來說,當您在分頁序列中前往下一頁或上一頁時,請根據您是要前往序列中較高或較低的頁面,考慮以不同方向滑動內容。
透過觀看轉換類型,您可以指派一或多個類型至 Active View 轉換。舉例來說,如果要在分頁序列中轉換至較高階的頁面,請使用 forwards
類型,如果要轉換至較低階的頁面,請使用 backwards
類型。只有在擷取或執行轉場效果時,這些類型才會生效,而且您可以透過 CSS 自訂每種類型,使用不同的動畫。
如要在同一個文件的檢視畫面轉場中使用類型,請將 types
傳遞至 startViewTransition
方法。為了允許此操作,document.startViewTransition
也接受以下物件:update
是更新 DOM 的回呼函式,而 types
則是具有類型的陣列。
const direction = determineBackwardsOrForwards();
const t = document.startViewTransition({
update: updateTheDOMSomehow,
types: ['slide', direction],
});
如要回應這些類型,請使用 :active-view-transition-type()
選取器。將要指定的 type
傳遞至選擇器。這樣一來,您就能將多個 View 轉場的樣式分開,不會互相干擾。
由於類型只會在擷取或執行轉換時套用,因此您可以使用選取器,針對具有該類型的檢視畫面轉換,在元素上設定或取消設定 view-transition-name
。
/* 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 (using the default root snapshot) */
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;
}
}
在以下分頁示範中,頁面內容會根據您前往的頁碼向前或向後滑動。在傳遞至 document.startViewTransition
時點選類型即可決定類型。
如要指定任何 Active View 轉換 (無論類型為何),您可以改用 :active-view-transition
虛擬類別選取器。
html:active-view-transition {
…
}
在檢視畫面轉換根層級使用類別名稱處理多個檢視畫面轉換樣式
有時從特定類型的檢視畫面轉換到另一種檢視畫面時,應有特別量身打造的轉場效果。或是「返回」導覽應與「轉寄」不同導覽。
在轉場類型推出前,處理這些情況的方法是在轉場根目錄上暫時設定類別名稱。呼叫 document.startViewTransition
時,此轉場根源為 <html>
元素,可透過 JavaScript 中的 document.documentElement
存取:
if (isBackNavigation) {
document.documentElement.classList.add('back-transition');
}
const transition = document.startViewTransition(() =>
updateTheDOMSomehow(data)
);
try {
await transition.finished;
} finally {
document.documentElement.classList.remove('back-transition');
}
如要在轉換完成後移除類別,此範例使用 transition.finished
,也就是在轉換達到結束狀態後解析的承諾。如要瞭解這個物件的其他屬性,請參閱 API 參考資料。
您現在可以在 CSS 中使用該類別名稱來變更轉場效果:
/* 'Forward' transitions */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms
cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Overrides for 'back' transitions */
.back-transition::view-transition-old(root) {
animation-name: fade-out, slide-to-right;
}
.back-transition::view-transition-new(root) {
animation-name: fade-in, slide-from-left;
}
與媒體查詢一樣,顯示的類別也可用來變更哪些元素可取得 view-transition-name
。
在不凍結其他動畫的情況下執行轉場效果
請觀看下方示範影片轉換位置:
你是否發現任何問題?請放心。以下是放慢的影片:
在轉場期間,影片似乎會暫停,然後播放的影片版本會淡入。這是因為 ::view-transition-old(video)
是舊檢視畫面的螢幕截圖,而 ::view-transition-new(video)
是新檢視畫面的「即時」圖片。
你可以修正這個問題,但請先問問自己是否值得修正。如果您沒有看見我不會改變它以正常速度播放音訊
如果需要修正,請勿顯示 ::view-transition-old(video)
;並直接切換至 ::view-transition-new(video)
。您可以覆寫預設樣式和動畫來執行這項操作:
::view-transition-old(video) {
/* Don't show the frozen old view */
display: none;
}
::view-transition-new(video) {
/* Don't fade the new view in */
animation: none;
}
這樣就大功告成了!
影片現在會在轉場期間播放。
使用 JavaScript 製作動畫
到目前為止,所有轉場效果都是使用 CSS 定義,但有時 CSS 不夠用:
這項轉換作業的某些部分無法單獨使用 CSS 達成:
- 動畫會從點擊位置開始播放。
- 動畫結束時,圓的半徑會延伸到最遠的角落。不過,我們希望日後 CSS 可以支援這項功能。
幸好,您可以使用 Web Animation API 建立轉場效果!
let lastClick;
addEventListener('click', event => (lastClick = event));
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// Get the click position, or fallback to the middle of the screen
const x = lastClick?.clientX ?? innerWidth / 2;
const y = lastClick?.clientY ?? innerHeight / 2;
// Get the distance to the furthest corner
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
);
// With a transition:
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
});
// Wait for the pseudo-elements to be created:
transition.ready.then(() => {
// Animate the root's new view
document.documentElement.animate(
{
clipPath: [
`circle(0 at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
],
},
{
duration: 500,
easing: 'ease-in',
// Specify which pseudo-element to animate
pseudoElement: '::view-transition-new(root)',
}
);
});
}
此範例使用 transition.ready
,承諾在轉換虛擬元素建立成功後即可解析。如要瞭解這個物件的其他屬性,請參閱 API 參考資料。
使用轉場效果來增強教學效果
View Transition API 的設計目的是「包裝」DOM 變更,並為其建立轉場效果。不過,轉場應視為強化功能,也就是說,如果 DOM 變更成功,但轉場失敗,應用程式不應進入「錯誤」狀態。理想情況下,轉場應不會失敗,但如果失敗,也不會影響其他使用者體驗。
為了將轉場效果視為強化效果,請注意不要在轉換失敗時,使用可能導致應用程式擲回的承諾。
async function switchView(data) { // Fallback for browsers that don't support this API: if (!document.startViewTransition) { await updateTheDOM(data); return; } const transition = document.startViewTransition(async () => { await updateTheDOM(data); }); await transition.ready; document.documentElement.animate( { clipPath: [`inset(50%)`, `inset(0)`], }, { duration: 500, easing: 'ease-in', pseudoElement: '::view-transition-new(root)', } ); }
這個範例的問題是,如果轉場效果無法達到 ready
狀態,switchView()
就會拒絕,但這並不表示檢視畫面無法切換。DOM 可能已成功更新,但有重複的 view-transition-name
,因此已略過轉換。
請改採以下做法:
async function switchView(data) { // Fallback for browsers that don't support this API: if (!document.startViewTransition) { await updateTheDOM(data); return; } const transition = document.startViewTransition(async () => { await updateTheDOM(data); }); animateFromMiddle(transition); await transition.updateCallbackDone; } async function animateFromMiddle(transition) { try { await transition.ready; document.documentElement.animate( { clipPath: [`inset(50%)`, `inset(0)`], }, { duration: 500, easing: 'ease-in', pseudoElement: '::view-transition-new(root)', } ); } catch (err) { // You might want to log this error, but it shouldn't break the app } }
此範例使用 transition.updateCallbackDone
等待 DOM 更新,失敗時則會拒絕。轉換失敗時,switchView
不會再拒絕,且會在 DOM 更新完成時解析,失敗時則拒絕。
如果您希望 switchView
在新檢視畫面「穩定」時解析,也就是所有動畫轉場已完成或略過至結尾,請將 transition.updateCallbackDone
替換為 transition.finished
。
不是 polyfill,但…
這並非容易以 polyfill 實作的功能。然而,這個輔助功能可讓您在不支援檢視轉換的瀏覽器中,更輕鬆地執行各項操作:
function transitionHelper({
skipTransition = false,
types = [],
update,
}) {
const unsupported = (error) => {
const updateCallbackDone = Promise.resolve(update()).then(() => {});
return {
ready: Promise.reject(Error(error)),
updateCallbackDone,
finished: updateCallbackDone,
skipTransition: () => {},
types,
};
}
if (skipTransition || !document.startViewTransition) {
return unsupported('View Transitions are not supported in this browser');
}
try {
const transition = document.startViewTransition({
update,
types,
});
return transition;
} catch (e) {
return unsupported('View Transitions with types are not supported in this browser');
}
}
運作方式如下:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
在不支援檢視轉場的瀏覽器中,系統仍會呼叫 updateDOM
,但不會提供動畫轉場效果。
您也可以在轉場期間提供一些 classNames
來新增至 <html>
,這樣就能更輕鬆地根據導覽類型變更轉場效果。
如果您不想顯示動畫,即使在支援檢視畫面轉場的瀏覽器中,也可以將 true
傳遞至 skipTransition
。如果網站的使用者偏好停用轉場效果,這項功能就能派上用場。
使用架構
如果您使用的是可抽象化 DOM 變更的程式庫或架構,則要知道 DOM 變更何時完成,就會比較困難。以下各架構範例使用上述說明,在不同架構下。
- React:此處的關鍵是
flushSync
,可同步套用一組狀態變更。是的,使用該 API 會顯示重大警告,但 Dan Abramov 向我保證,在這種情況下使用該 API 是適當的。與 React 和非同步程式碼一樣,使用startViewTransition
傳回的各項承諾時,請確保程式碼以正確的狀態執行。 - Vue.js:這裡的鍵是
nextTick
,在 DOM 更新後執行。 - Svelte:與 Vue 非常相似,但等待下一次變更的方法是
tick
。 - Lit:此處的重點是元件中的
this.updateComplete
應許,在 DOM 更新後會完成。 - Angular:這裡的鍵是
applicationRef.tick
,可清除待處理的 DOM 變更。自 Angular 17 版起,您可以使用@angular/router
隨附的withViewTransitions
。
API 參考資料
const viewTransition = document.startViewTransition(update)
建立新的
ViewTransition
。擷取文件目前狀態時,系統會呼叫
update
函式。接著,當
updateCallback
傳回的承諾已兌現時,轉換作業就會在下一個影格開始。如果updateCallback
傳回的承諾遭拒,系統就會捨棄轉換作業。const viewTransition = document.startViewTransition({ update, types })
使用指定類型啟動新的
ViewTransition
擷取文件的目前狀態後,系統會呼叫
update
。types
會在擷取或執行轉場時,設定轉場的有效類型。這個目錄一開始是空的。詳情請參閱viewTransition.types
。
ViewTransition
的例項成員:
viewTransition.updateCallbackDone
保證可履行
updateCallback
傳回的承諾,或是在拒絕時拒絕。View Transition API 會包裝 DOM 變更並建立轉場效果。不過,有時您並不在意轉換動畫是否成功或失敗,而是想瞭解 DOM 是否發生變更以及何時發生。
updateCallbackDone
適用於該用途。viewTransition.ready
當建立轉場的虛擬元素,且動畫即將開始時,就會執行的承諾。
無法開始轉換時,則會拒絕。這可能是因為設定有誤,例如重複的
view-transition-name
,或是updateCallback
傳回了遭拒的承諾。viewTransition.finished
當結束狀態完全顯示且可供使用者互動時,就會執行的承諾。
只有在
updateCallback
傳回已遭拒的承諾時才會拒絕,因為這表示未建立結束狀態。否則,如果轉換無法開始,或是在轉場期間略過,仍會達到結束狀態,因此
finished
就會執行完畢。viewTransition.types
類似
Set
的物件,可保留有效的檢視畫面轉場類型。如要操作項目,請使用其例項方法clear()
、add()
和delete()
。如要在 CSS 中回應特定類型的要求,請使用轉換根層級的
:active-view-transition-type(type)
虛擬類別選取器。檢視畫面轉場完成後,系統會自動清理類型。
viewTransition.skipTransition()
略過轉場動畫部分。
由於 DOM 變更與轉場無關,因此不會略過呼叫
updateCallback
。
預設樣式和轉場參考資料
::view-transition
- 根虛擬元素會填滿可視區域,並包含每個
::view-transition-group
。 ::view-transition-group
絕對定位。
「之前」之間的轉換
width
和height
和「after」州。在「之前」之間轉場的
transform
和「after」選取區域空間四捨五入::view-transition-image-pair
絕對會供人加入。
具有
isolation: isolate
,可限制mix-blend-mode
對舊版和新版檢視畫面的影響。::view-transition-new
和::view-transition-old
絕對位於包裝函式左上角。
會填滿 100% 的群組寬度,但高度為自動,因此會維持其顯示比例,而非填滿群組。
具有
mix-blend-mode: plus-lighter
可允許真正的交叉漸變效果。舊版檢視畫面會從
opacity: 1
轉換為opacity: 0
。新檢視畫面會從opacity: 0
轉換為opacity: 1
。
意見回饋
向來都歡迎您提供開發人員的意見回饋。如要這樣做,請在 GitHub 上向 CSS 工作小組回報問題,並提供建議和問題。在問題名稱前方加上 [css-view-transitions]
。
如果您遇到問題,請改為回報 Chromium 錯誤。