相同文件轉場效果

網頁功能: 檢視轉換

問題說明

網站通常會提供物件的多個檢視畫面,例如產品清單,以及每項產品的詳細資料頁面。在兩個檢視畫面之間切換時,往往會感到不連貫。使用者點選產品縮圖查看詳細資料時,縮圖會消失,螢幕其他位置會立即顯示新的放大圖片。這種不連續性會讓使用者更難追蹤元素之間的關係。

解決方法

View Transitions API 可讓您指定在轉換前後存在於不同狀態的元素組合。在單頁導覽 (SPA) 中使用 document.startViewTransition() 觸發轉場效果時,瀏覽器會根據共用的不重複 view-transition-name 識別這些共用元素。接著,系統會自動計算位置、大小和樣式的差異,並流暢地從舊狀態轉換為新狀態。這項轉換會在頂層進行,甚至高於 z-index 值較高的元素。

導入指南

步驟 1:在 startViewTransition 中包裝狀態變更

如果是單頁應用程式 (SPA) 或簡單的狀態變更,請將更新 DOM 的邏輯包裝在 document.startViewTransition 中。瀏覽器會擷取目前狀態的快照、執行更新,然後擷取新狀態。

function navigate(view) {
  // MANDATORY: Wrap the update in startViewTransition
  document.startViewTransition(() => updateDOM(view));
}

步驟 2:指派共用轉場效果名稱

使用 view-transition-name CSS 屬性,告知瀏覽器應變形哪些元素。名稱可以是任何值 (none 除外)。強制規定:指定 view-transition-name 的前後最多只能有 1 個元素。如果有多個元素具有相同的 view-transition-name,DOM 會立即更新為新狀態,不會有轉場效果。

您可以使用多個 view-transition-name 變形多組元素。舉例來說,您可能想分別為產品圖片和標題設定轉場效果。

由於清單檢視畫面中有多個項目,因此無法為所有項目指定相同的 view-transition-name。在單頁應用程式中,這個問題有兩種解決方式。

  1. 動態詳細資料頁面:為清單頁面上的每個項目指派不重複的 view-transition-name,然後在選取清單項目時,將該名稱動態套用至詳細資料頁面上的相符元素,如下所示。

    /* In the list view, give each */
    #product-1 { view-transition-name: p1 }
    #product-2 { view-transition-name: p2 }
    #product-3 { view-transition-name: p3 }
    
    function updateDOM(clickedTransitionName){
      const hero = document.getElementById("hero");
      hero.style.viewTransitionName = clickedTransitionName;
    }
    
  2. 動態清單項目:為詳細資料頁面上的元素指派 view-transition-name,並在選取清單頁面上的項目時,將該名稱套用至該項目。返回清單頁面時,從清單頁面上的項目中移除 view-transition-name。

詳細資料頁面上的 #hero 元素和清單頁面上選取的 .thumbnail 元素會共用 view-transition-name。

#hero{
  view-transition-name: hero;
}
.thumbnail.selected {
  view-transition-name: hero;
}

點選縮圖時,我們需要使用 .selected 類別選取器指派 view-transition-name,並在開始轉換前對 DOM 進行任何變更,藉此準備清單檢視。

接著,您可以呼叫 document.startViewTransition(),並套用變更,將頁面從詳細資料檢視畫面轉換為清單檢視畫面。

返回清單檢視畫面後,您必須清除檢視畫面轉換類別,以免下次導覽時發生錯誤。轉換的 finished 承諾解析後,即可執行這項清除作業。

// Function called when a thumbnail is clicked
function goFromListToDetail(e){
  e.currentTarget.classList.add("selected");
  const hero = document.getElementById("hero");
  const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
  hero.style.background = bgColor;

  // Trigger the transition, checking for support
  if (!document.startViewTransition) {
    document.body.classList.add("detail");
    // MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
    document.getElementById("detail-heading")?.focus();
    return; // MANDATORY: End function execution if view transitions are not supported.  
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.add("detail");
  });
  // MANDATORY Accessibility Routing: Route focus after the view transition resolves
  transition.finished.finally(() => {
    document.getElementById("detail-heading")?.focus();
  });
}

// Function called when navigating from detail back to list view
function goFromDetailToList() {
  if (!document.startViewTransition) {
    document.body.classList.remove("detail");
    document.getElementById("list-heading")?.focus();
    return;
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.remove("detail");
  });
  // Clean up the list view and route focus
  transition.finished.finally(() => {
    // Route focus back to list view
    document.getElementById("list-heading")?.focus();
    // Remove selected classList to remove view-transition-names
    document.querySelectorAll(".selected").forEach(
      (element) => {
        element.classList.remove("selected");
      },
    );
  });
}

請依用途選擇合適的方法。動態清單項目需要較少的重複 CSS,但需要更多手動 JavaScript 清理。

步驟 3:修正顯示比例「拉伸」問題

根據預設,瀏覽器會在群組內淡入淡出新舊快照,並調整群組大小以容納兩者。如果您要轉換文字,請在舊檢視畫面和新檢視畫面中,將文字元素的寬度設為 fit-content,這樣轉換後的元素顯示比例就會保持穩定。

#list-page .title {
  width: fit-content;
}

#detail-page #title {
  width: fit-content;
}

如果轉換的元素會變更顯示比例,可能需要將舊和新虛擬元素的高度設為 ::view-transition-pair() 虛擬元素的 100%。

::view-transition-old(hero),
::view-transition-new(hero){
  height: 100%;
}

虛擬元素是即時元素的快照,因此您也可以使用 object-fit 和 object-position 宣告,進一步控管轉場效果。

最佳做法

  • 請勿指定過多轉場效果。只在使用者主動追蹤的主要內容 (例如主頁橫幅、標題) 使用共用元素。
  • 務必在轉場效果結束後移除暫時的 view-transition-name 值,以免對日後的轉場效果造成副作用。
  • 請勿轉換含有有效動畫的元素。檢視畫面轉換會對快照進行操作,因此在檢視畫面轉換期間,所有動畫都會暫停。
  • 務必使用 prefers-reduced-motion 媒體查詢,尊重使用者減少動態效果的偏好設定。
  • 強制無障礙路由:檢視畫面轉換會動態變更頁面版面配置,但不會管理程式化焦點。如果焦點仍停留在轉換期間隱藏或移除的元素上,焦點就會消失,導致鍵盤和輔助技術使用者無法瞭解上下文。在 DOM 更新後,或檢視畫面轉換的 finished 承諾解析後,立即透過程式輔助方式將焦點移至更新後的頁面標題或檢視容器 (使用 tabindex="-1")。
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

備用策略

View Transitions API 專為漸進增強而設計。不支援的瀏覽器則會立即執行 DOM 更新,不會顯示動畫。

function navigate(){
  if (!document.startViewTransition) {
    // Fallback: Just update the DOM
    updateDOM();
  } else {
    document.startViewTransition(() => updateDOM());
  }
}