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

在單一文件上執行檢視轉場效果時,稱為「同文件檢視轉場效果」。這通常是使用 JavaScript 更新 DOM 的單頁應用程式 (SPA)。自 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」面板很適合用來偵錯轉場效果。

您可以使用「動畫」面板暫停下一個動畫,然後來回切換動畫。在這段期間,您可以在「Elements」(元素) 面板中找到轉場虛擬元素。

使用 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 時,整個圖片會「取消裁剪」。

詳情請參閱 (查看轉換:處理顯示比例變更)(https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/)


使用媒體查詢變更不同裝置狀態的轉場效果

建議您在行動裝置和電腦上使用不同的轉場效果,例如以下示範在行動裝置上從側邊播放整張投影片,不過在電腦上是效果更佳的投影片:

從一個元素轉換到另一個元素。極簡演示來源

方法是使用一般媒體查詢:

/* 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-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 變更成功,但轉換失敗,您的應用程式就不應進入「error」狀態。理想情況下,轉換作業應不會失敗,但如能順利轉換,應該也不會導致其餘使用者體驗中斷。

為了將轉場效果視為強化效果,請小心不要使用轉換承諾,避免在轉換失敗時擲回應用程式。

錯誤做法
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 在新的檢視畫面「settled」時解析,就像在任何動畫轉場完成或略過一樣,請將 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,但不會呈現動畫轉場效果。

您也可以提供在轉場期間為 <html> 加入一些 classNames,以便根據導覽類型變更轉場效果

如果不想使用動畫,也可以將 true 傳遞至 skipTransition,即使瀏覽器支援檢視轉場效果也一樣。如果網站有使用者偏好停用轉場功能,這項功能就能派上用場。


使用架構

如果您使用的是程式庫或架構,而該程式庫或架構可以抽離 DOM 變更,最困難的部分就是瞭解 DOM 何時完成 DOM。以下為在不同架構中使用上述輔助程式的範例。


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 的物件,可保留 Active View 轉場類型。如要操控項目,請使用其執行個體方法 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 Working Group 回報問題並提供建議和問題。在問題前方加上 [css-view-transitions]

如果您遇到錯誤,請改為回報 Chromium 錯誤