單頁應用程式適用的相同文件檢視轉換功能

當在單一文件上執行檢視畫面轉換時,即稱為「相同文件檢視轉換」。這通常是在單頁應用程式 (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: 1opacity: 0 的動畫效果,新檢視畫面則會從 opacity: 0opacity: 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 以相同方式為多個虛擬元素製作動畫

瀏覽器支援

  • 125
  • 125
  • x
  • x

假設你有兩個資訊卡的檢視畫面轉換,但同時也是頁面標題。如要為標題以外的所有資訊卡套用動畫效果,您必須編寫指定各張資訊卡的選取器。

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 個選取器。要新增元素嗎?接著,您也需要擴大可套用動畫樣式的選取器。不完全可擴充。

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)

資訊卡示範錄影內容。使用 view-transition-class 時,系統會將相同的 animation-timing-function 套用至所有資訊卡,但新增或移除的資訊卡除外。

偵錯轉場效果

由於檢視畫面轉場效果是以 CSS 動畫為基礎,因此 Chrome 開發人員工具中的「動畫」面板很適合用來偵錯轉場效果。

使用「Animations」面板,您可以暫停下一個動畫,然後透過拖曳的方式瀏覽動畫。在此期間,您可以在「元素」面板中找到轉場虛擬元素。

使用 Chrome 開發人員工具對檢視畫面轉場效果進行偵錯。

轉場效果的元素不一定要是同一個 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;
  }
}

不過,優先使用「減少動態效果」,並不代表使用者想要「沒有動態效果」。比起先前的程式碼片段,您也可以選擇更精巧的動畫,但其中仍表示元素與資料流動的關係。


使用檢視畫面轉換類型處理多個檢視畫面轉換樣式

有時在轉換到另一個檢視畫面時,應有特別量身打造的轉場效果。舉例來說,在前往分頁序列的下一頁或上一頁時,您可能會想根據前往順序較高的頁面還是順序較低的頁面,往不同方向滑動內容。

Pagination 示範錄製過程。系統會根據您前往的頁面,採用不同的轉場效果。

透過觀看轉換類型,您可以指派一或多個類型至 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-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 確認這種狀況適合我。與 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 傳回了遭拒的承諾。

適合使用 JavaScript 為轉換虛擬元素加上動畫效果

viewTransition.finished

在使用者完全可見並互動後執行履行的承諾。

只有在 updateCallback 傳回遭拒的承諾時,才會拒絕,因為這表示結束狀態尚未建立。

否則,如果轉換無法開始,或是在轉換期間略過,仍會達到結束狀態,因此 finished 就會執行完畢。

viewTransition.types

包含 Set 之類的物件,保留有效檢視畫面轉換的類型。如要操控項目,請使用其例項方法 clear()add()delete()

如要在 CSS 中回應特定類型的要求,請使用轉換根層級的 :active-view-transition-type(type) 虛擬類別選取器。

檢視畫面轉換完成時,系統會自動清除類型。

viewTransition.skipTransition()

略過轉場效果的動畫部分。

這不會略過呼叫 updateCallback,因為 DOM 變更與轉場是各自獨立的。


預設樣式和轉場效果參考資料

::view-transition
根虛擬元素會填滿可視區域,並包含每個 ::view-transition-group
::view-transition-group

絕對定位。

在「之前」和「之後」狀態之間轉換 widthheight

在「之前」和「之後」可視區域四捨五入之間轉換 transform

::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 錯誤