一致的跨文件轉場效果

問題說明

跨文件檢視畫面轉場效果,可在相同來源導覽期間,為兩個頁面之間的元素建立動畫。瀏覽器會擷取舊網頁的快照、前往新網頁,然後從快照動畫轉換至新網頁。如果新頁面尚未完成載入重要資源 (樣式表、版面配置指令碼或重要 DOM 元素),轉換動畫會呈現不完整或未套用樣式的狀態。這會導致視覺錯誤,例如元素變形到錯誤位置、內容在動畫中途重新排列,或備用字型在轉場完成後閃爍成網頁字型。

解決方法

在新網頁的 <head> 中,對重要的 <link> 和 <script> 元素使用 blocking="render",並使用 <link rel="expect"> 封鎖轉譯,直到特定 DOM 元素剖析完畢。這樣一來,瀏覽器就會等到新頁面的視覺狀態穩定後,才開始播放檢視畫面轉換動畫。瀏覽器會在背景繼續剖析 HTML,只有繪製作業會延遲。

導入策略

  1. 必要:在兩個頁面上使用 @view-transition CSS at 規則,選擇加入跨文件檢視畫面轉場效果。
  2. 必要:請確保重要樣式表位於 <head> 中。根據預設,<head> 中的樣式表會阻斷轉譯。動態插入的樣式表需要明確的 blocking="render"。
  3. 務必在必須於轉場動畫播放前執行的 <script> 元素上使用 blocking="render" (例如套用主題或影響版面配置的指令碼)。
  4. 務必使用 <link rel="expect" href="#element-id" blocking="render"> 封鎖轉譯,直到不需捲動位置的內容剖析完畢。這適用於所有轉場類型:全頁淡出 (避免動畫轉場至空白頁面)、變形動畫 (確保 DOM 中存在具名元素),以及依附指令碼的版面配置 (確保剖析樣式內容)。
  5. 請勿封鎖非重要內容的算繪。只封鎖會影響初始可視區域的資源和元素。如果封鎖過多內容,會延遲轉場效果,並降低感知效能。

導入指南

步驟 1:選擇加入跨文件檢視轉場效果

必要:來源和到達網頁都必須包含 @view-transition at 規則。否則不會發生跨文件轉場效果。

/*
  MANDATORY: Include this rule in every page that participates
  in cross-document view transitions.
  `navigation: auto` enables transitions for standard navigations
  (link clicks, form submissions, back/forward).
*/
@view-transition {
  navigation: auto;
}

/* MANDATORY Copy-Paste Safety: Disable cross-document view transitions for users requesting reduced motion */
@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }
}

步驟 2:在重要指令碼執行前,禁止轉譯

如果 <head> 中的非阻塞指令碼必須在轉換動畫播放前執行 (例如套用主題類別或影響版面配置),請使用 blocking="render" 標記。如果沒有這個屬性,async、defer 或 type="module" 指令碼可能會在轉場效果開始後才執行。

<head>
  <!--
    DO: Mark layout-critical scripts with blocking="render".
  -->
  <script type=module blocking="render">
    // Example: apply a stored theme before the page renders,
    // so the transition snapshot reflects the correct theme.
    document.documentElement.dataset.theme =
      localStorage.getItem('theme') || 'light';
  </script>
</head>

步驟 3:在剖析重要 DOM 元素前,禁止轉譯

<head> 中的樣式表和 blocking="render" 指令碼只能保證 <head> 已完整處理。不會等待任何 <body> 內容剖析完成。如果沒有額外的封鎖,瀏覽器可能會在不需捲動位置元素存在於 DOM 之前,擷取新頁面快照,導致轉換動畫顯示空白或部分算繪的頁面。

<link rel="expect"> 會封鎖算繪作業,直到剖析特定元素 (由 id 識別) 為止,藉此解決這個問題。href 值必須是片段 ID (例如 #hero),且與目標元素的 id 屬性相符。剖析該元素的結尾標記後,系統就會釋出轉譯區塊。請注意,<link rel="expect"> 僅保證目標 DOM 元素存在於文件中,不會等待外部子資源 (例如 <img> 像素) 下載或解碼。

務必在下列所有情況下使用 <link rel="expect">:

應用實例 1:全頁淡入淡出

即使沒有任何個別元素具有 view-transition-name,預設的 root 轉場效果也會淡出整個頁面。如果新頁面的快照是在剖析不需捲動位置內容之前拍攝,則淡出淡入動畫會從舊頁面轉換為空白或不完整的頁面。在標示不需捲動位置可見內容結尾的元素上,封鎖算繪。

<head>
  <link rel="stylesheet" href="/css/styles.css">

  <!--
    DO: Block rendering until the main content area is parsed,
    even for a simple cross-fade. Without this, the browser may
    snapshot the page before visible content exists in the DOM,
    causing the cross-fade to reveal a blank or partial page.
  -->
  <link rel="expect" href="#main-content" blocking="render">
</head>
<body>
  <header>...</header>
  <main id="main-content">
    <h1>Page Title</h1>
    <p>Above-the-fold content the user should see immediately.</p>
  </main>
  <!-- Content below the fold does NOT need to be blocked on -->
  <section>...</section>
</body>

應用實例 2:特定元素之間的變形動畫

如果兩個網頁上的元素共用 view-transition-name,瀏覽器會在導覽時流暢地變換這些元素。如果轉場效果開始時,目標元素尚未剖析,瀏覽器就無法找到該元素,變形效果會降級為個別的退出和進入動畫。在剖析含有 view-transition-name 的元素前,禁止轉譯。

由於 <link rel="expect"> 只會暫停算繪,直到目標 DOM 元素剖析完成,不會等待圖片資料下載或解碼,因此未快取的 <img> 可能會在新頁面快照中擷取為空白矩形。為避免在圖片變形期間出現空白快照或版面配置偏移,請預先載入或預先解碼重要的轉場圖片 (例如使用 <link rel="preload" as="image"> 或預先算繪到達網頁),並提供明確的大小備用方案 (width 和 height 屬性或 CSS aspect-ratio,以及預留位置背景顏色)。

<head>
  <link rel="stylesheet" href="/css/styles.css">

  <!--
    DO: Preload critical transition images so their pixels are
    available to decode before the new-page snapshot is captured.
    <link rel="expect"> only waits for the DOM element to be parsed,
    NOT for image pixels to download or decode.
  -->
  <link rel="preload" as="image" href="/img/product.webp" fetchpriority="high">

  <!--
    DO: Block rendering until the element participating in the
    morph animation has been parsed. Without this, the browser
    may start the transition before #hero exists, causing the
    morph to degrade to a fade-out/fade-in.
  -->
  <link rel="expect" href="#hero" blocking="render">

  <!--
    When multiple blocking="render" resources are present,
    rendering is blocked until ALL of them are satisfied.
    Here, the browser waits for both the script to execute
    AND the #hero element to be parsed — whichever comes last.
  -->
  <script async blocking="render" src="/js/transition-setup.js"></script>
</head>
<body>
  <header>...</header>
  <section id="hero">
    <h1 style="view-transition-name: page-title">Product Name</h1>
    <!--
      DO: Provide explicit dimensions (width/height or aspect-ratio)
      as a sizing fallback so the snapshot captures the correct layout
      box even if image decoding is still in progress.
    -->
    <img
      style="view-transition-name: hero-image"
      src="/img/product.webp"
      width="800"
      height="600"
      alt="Product"
    >
  </section>
</body>

步驟 4:使用媒體查詢,避免轉譯作業因回應式設計而受阻

不同可視區域大小,不需捲動位置顯示的內容數量可能不同。在 <link rel="expect"> 上使用 media 屬性,只針對特定可視區域寬度可見的內容,封鎖算繪。

<head>
  <!--
    DO: Use media queries to conditionally block rendering.
    On wide screens, both the hero and the sidebar are visible,
    so block until both are parsed. On narrow screens, only the
    hero is visible initially.
  -->
  <link
    rel="expect"
    href="#hero"
    blocking="render"
    media="screen and (width <= 768px)"
  >
  <link
    rel="expect"
    href="#sidebar"
    blocking="render"
    media="screen and (width > 768px)"
  >
</head>

步驟 5:使用 pagereveal 進行情境相關轉場 (選用)

pagereveal 事件並非核心轉譯阻斷策略的必要條件。只有在必須根據使用者導覽來源動態指派 view-transition-name 值時,才需要使用這個屬性,例如將特定清單項目變形為詳細資料頁面標題。

如果 view-transition-name 值是在 CSS 中靜態指派,或您只使用預設的全頁淡出效果,請完全略過這個步驟。

<head>
  <!--
    MANDATORY: The pagereveal listener must be registered before
    the page renders. Inline small setup scripts with blocking="render"
    to avoid an extra network round-trip on cold loads, or use an
    external async script with blocking="render" so the listener is
    registered early without blocking HTML parsing.
    If the listener is registered too late (e.g., in a deferred
    script), the event may have already fired.
  -->
  <script async blocking="render" src="/js/transition-setup.js"></script>
</head>
// transition-setup.js
window.addEventListener('pagereveal', async (event) => {
  if (!event.viewTransition) return;

  const from = navigation.activation?.from;
  if (!from) return;

  const fromUrl = new URL(from.url);

  // DO: Assign view-transition-name based on navigation context.
  // This enables a morph animation from the product card on the
  // list page to the heading on the detail page.
  if (fromUrl.pathname === '/products/') {
    const heading = document.querySelector('main h1');
    if (!heading) return;

    heading.style.viewTransitionName = 'product-title';

    try {
      await event.viewTransition.ready;
    } catch (error) {
      // Skipped view transitions reject `ready` with InvalidStateError.
      if (error.name !== 'InvalidStateError') {
        throw error;
      }
    } finally {
      // MANDATORY: Remove the temporary name after the transition
      // finishes. Stale names interfere with subsequent navigations
      // and prevent the page from entering the bfcache.
      await event.viewTransition.finished;
      heading.style.viewTransitionName = '';
    }
  }
});

最佳做法

  • 建議盡可能透過 CSS 指派 view-transition-name。如果名稱取決於導覽情境,請保留 JavaScript 指派 (透過 pagereveal)。
  • 建議:讓會阻礙轉譯的指令碼保持精簡,並快速執行。瀏覽器內建逾時機制 (約 4 秒),超過這個時間就會完全略過轉場效果 (如果已啟動轉場效果,則會拒絕 viewTransition.ready 並傳回 InvalidStateError)。
  • 請勿使用 <link rel="expect"> 封鎖網頁深處、初始可視區域中看不到的元素。這會延遲轉換,且不會帶來視覺效果。
  • 請勿使用 blocking="render" 標記數據分析、廣告聯播網、代碼管理工具或其他第三方載入器,即使這些載入器會將內容插入初始可視區域也一樣。其網路延遲時間會計入轉換逾時,且輸出內容不屬於網頁的穩定狀態。
  • 請勿將相同的 view-transition-name 指派給同一頁面上的多個元素。如果名稱重複,系統會略過整個轉換作業。
  • 輔助技術時間影響:使用 blocking="render" 會延遲視覺更新和初始繪製。雖然這可避免視力正常的使用者看到視覺錯誤,但可能會導致螢幕閱讀器和其他輔助技術 (仰賴轉譯的無障礙樹狀結構) 發生處理延遲或延遲初始化。請權衡視覺連續性優勢與非視覺使用者的初始讀取延遲時間,並確保轉譯阻斷指令碼數量最少且經過高度最佳化。

備用策略

跨文件檢視畫面轉換非常適合用於漸進增強。在不支援的瀏覽器中,系統會忽略 @view-transition 規則,並按照沒有這項功能時的方式,執行標準同源導覽。支援的瀏覽器會顯示流暢的轉場效果,其他瀏覽器則會顯示標準導覽。瀏覽器支援有限,不應成為避免採用的理由。

所有支援跨文件檢視畫面轉換的瀏覽器,也都支援 blocking="render" 和 <link rel="expect">,因此本指南所述的轉譯阻斷功能不需要個別備用方案。

其他考量

  1. 效能影響:每個會阻礙轉譯的資源都會延遲檢視畫面轉換動畫的開始時間。盡量減少會阻礙轉譯的指令碼數量,並只在不需捲動位置的元素中使用 <link rel="expect">。使用 Speculation Rules API 預先算繪到達網頁,完全消除載入延遲。
  2. 逾時行為:如果轉譯阻斷時間總計超過約 4 秒,瀏覽器會略過轉場效果 (且任何進行中的 viewTransition.ready 承諾都會遭到拒絕,並顯示 InvalidStateError)。請確保重要資源在這個時間範圍內順利載入。
  3. bfcache 相容性:如果未在轉換後清除暫時的 view-transition-name 指派,可能會導致網頁無法進入 bfcache。請務必在 finished 回呼中移除動態指派的名稱。