Chuyển đổi chế độ xem nhiều tài liệu cho các ứng dụng nhiều trang

Khi chuyển đổi chế độ xem xảy ra giữa hai tài liệu khác nhau, quá trình này được gọi là chuyển đổi chế độ xem giữa nhiều tài liệu. Trường hợp này thường xảy ra trong các ứng dụng nhiều trang (MPA). Hiệu ứng chuyển đổi chế độ xem trên nhiều tài liệu được hỗ trợ trong Chrome từ Chrome 126.

Hỗ trợ trình duyệt

  • Chrome: 126.
  • Cạnh: 126.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Việc chuyển đổi chế độ xem trên nhiều tài liệu dựa trên các thành phần và nguyên tắc giống như chuyển đổi chế độ xem trên cùng một tài liệu, điều này là có chủ ý:

  1. Trình duyệt sẽ chụp nhanh các phần tử có view-transition-name duy nhất trên cả trang cũ và trang mới.
  2. DOM được cập nhật trong khi quá trình kết xuất bị chặn.
  3. Cuối cùng, hiệu ứng chuyển đổi sử dụng ảnh động CSS.

Điểm khác biệt khi so sánh với các hiệu ứng chuyển đổi chế độ xem cùng một tài liệu là với hiệu ứng chuyển đổi chế độ xem trên nhiều tài liệu, bạn không cần gọi document.startViewTransition để bắt đầu chuyển đổi chế độ xem. Thay vào đó, điều kiện kích hoạt để chuyển đổi chế độ xem trên nhiều tài liệu là thao tác điều hướng cùng nguồn gốc từ trang này sang trang khác. Hành động này thường do người dùng trên trang web của bạn thực hiện khi nhấp vào một đường liên kết.

Nói cách khác, không có API nào cần gọi để bắt đầu chuyển đổi chế độ xem giữa hai tài liệu. Tuy nhiên, bạn cần đáp ứng 2 điều kiện:

  • Cả hai tài liệu cần phải có trên cùng một nguồn gốc.
  • Cả hai trang đều cần phải chọn tham gia để cho phép chuyển đổi chế độ xem.

Cả hai điều kiện này sẽ được giải thích sau trong tài liệu này.


Hiệu ứng chuyển đổi chế độ xem trên nhiều tài liệu chỉ áp dụng cho các thao tác trên cùng nguồn gốc

Hiệu ứng chuyển đổi chế độ xem trên nhiều tài liệu chỉ áp dụng cho thao tác trên cùng nguồn gốc. Các điều hướng được coi là có cùng nguồn gốc nếu nguồn gốc của cả hai trang tham gia đều giống nhau.

Nguồn gốc của một trang là sự kết hợp giữa lược đồ, tên máy chủ và cổng đã sử dụng, như thông tin chi tiết trên web.dev.

Một URL ví dụ có lược đồ, tên máy chủ và cổng được làm nổi bật. Kết hợp với nhau, chúng tạo thành nguồn gốc.
Một URL mẫu trong đó lược đồ, tên máy chủ và cổng được làm nổi bật. Kết hợp với nhau, chúng tạo thành nguồn gốc.

Ví dụ: bạn có thể chuyển đổi chế độ xem trên nhiều tài liệu khi điều hướng từ developer.chrome.com đến developer.chrome.com/blog, vì các chế độ xem này có cùng nguồn gốc. Bạn không thể tạo hiệu ứng chuyển đổi đó khi điều hướng từ developer.chrome.com đến www.chrome.com, vì các lượt chuyển đổi đó có nhiều nguồn gốc và cùng một trang web.


Chọn sử dụng tính năng chuyển đổi chế độ xem trên nhiều tài liệu

Để chuyển đổi chế độ xem trên nhiều tài liệu giữa hai tài liệu, cả hai trang tham gia cần phải chọn cho phép việc này. Việc này được thực hiện bằng quy tắc @ @view-transition trong CSS.

Trong quy tắc @view-transition, hãy đặt chỉ số mô tả navigation thành auto để bật tính năng chuyển đổi chế độ xem cho các thao tác điều hướng cùng lúc trên nhiều tài liệu.

@view-transition {
  navigation: auto;
}

Bằng cách đặt chỉ số mô tả navigation thành auto, bạn đang chọn cho phép chuyển đổi chế độ xem cho các NavigationType sau:

  • traverse
  • push hoặc replace, nếu quá trình kích hoạt không phải do người dùng khởi tạo thông qua cơ chế giao diện người dùng của trình duyệt.

Các thành phần điều hướng bị loại trừ khỏi auto, chẳng hạn như thao tác điều hướng bằng thanh địa chỉ URL hoặc nhấp vào dấu trang, cũng như mọi hình thức người dùng hoặc tập lệnh đã bắt đầu tải lại.

Nếu quá trình điều hướng mất quá nhiều thời gian (trong trường hợp của Chrome là hơn 4 giây), thì hiệu ứng chuyển đổi khung hiển thị sẽ bị bỏ qua bằng TimeoutError DOMException.

Bản minh hoạ về cách chuyển đổi giữa nhiều tài liệu

Hãy tham khảo bản minh hoạ sau đây về việc sử dụng hiệu ứng chuyển đổi khung hiển thị để tạo bản minh hoạ Stack Navigator. Không có lệnh gọi nào đến document.startViewTransition() ở đây, hiệu ứng chuyển đổi chế độ xem được kích hoạt bằng cách điều hướng từ trang này sang trang khác.

Ghi lại bản minh hoạ Trình điều hướng ngăn xếp. Yêu cầu phải có Chrome 126 trở lên.

Tuỳ chỉnh hiệu ứng chuyển đổi giữa chế độ xem trên nhiều tài liệu

Để tuỳ chỉnh hiệu ứng chuyển đổi giữa nhiều tài liệu, bạn có thể dùng một số tính năng của nền tảng web.

Các tính năng này không thuộc thông số kỹ thuật của View Transition API, nhưng được thiết kế để sử dụng cùng với các tính năng này.

Sự kiện pageswappagereveal

Hỗ trợ trình duyệt

  • Chrome: 124.
  • Cạnh: 124.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Để cho phép bạn tuỳ chỉnh hiệu ứng chuyển đổi giữa các chế độ xem trên nhiều tài liệu, thông số kỹ thuật HTML bao gồm 2 sự kiện mới mà bạn có thể sử dụng: pageswappagereveal.

Hai sự kiện này được kích hoạt cho mọi thao tác điều hướng trên nhiều tài liệu cùng nguồn gốc, bất kể quá trình chuyển đổi khung hiển thị có sắp xảy ra hay không. Nếu một lượt chuyển đổi chế độ xem sắp xảy ra giữa hai trang, bạn có thể truy cập vào đối tượng ViewTransition bằng cách sử dụng thuộc tính viewTransition trên các sự kiện này.

  • Sự kiện pageswap kích hoạt trước khi khung cuối cùng của trang được hiển thị. Bạn có thể sử dụng tính năng này để thực hiện một số thay đổi vào phút chót trên trang gửi đi, ngay trước khi các ảnh chụp nhanh cũ được chụp.
  • Sự kiện pagereveal kích hoạt trên một trang sau khi được khởi chạy hoặc kích hoạt lại, nhưng trước cơ hội hiển thị đầu tiên. Với tính năng này, bạn có thể tuỳ chỉnh trang mới trước khi chụp ảnh nhanh.

Ví dụ: bạn có thể dùng các sự kiện này để đặt hoặc thay đổi nhanh một số giá trị view-transition-name, hoặc truyền dữ liệu từ tài liệu này sang tài liệu khác bằng cách ghi và đọc dữ liệu từ sessionStorage để tuỳ chỉnh hiệu ứng chuyển đổi khung hiển thị trước khi chạy thực sự.

let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
  if (event.target.tagName.toLowerCase() === 'a') return;
  lastClickX = event.clientX;
  lastClickY = event.clientY;
});

// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
  if (event.viewTransition && lastClick) {
    sessionStorage.setItem('lastClickX', lastClickX);
    sessionStorage.setItem('lastClickY', lastClickY);
  }
});

// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
  if (event.viewTransition) {
    lastClickX = sessionStorage.getItem('lastClickX');
    lastClickY = sessionStorage.getItem('lastClickY');
  }
});

Nếu muốn, bạn có thể quyết định bỏ qua chuyển đổi trong cả hai sự kiện.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

Đối tượng ViewTransition trong pageswappagereveal là hai đối tượng khác nhau. Họ cũng xử lý nhiều lời hứa theo cách khác nhau:

  • pageswap: Sau khi tài liệu bị ẩn, đối tượng ViewTransition cũ sẽ bị bỏ qua. Khi điều đó xảy ra, viewTransition.ready sẽ từ chối và viewTransition.finished xử lý.
  • pagereveal: Lời hứa updateCallBack đã được giải quyết tại thời điểm này. Bạn có thể sử dụng lời hứa viewTransition.readyviewTransition.finished.

Hỗ trợ trình duyệt

  • Chrome: 123.
  • Cạnh: 123.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Trong cả hai sự kiện pageswappagereveal, bạn cũng có thể thực hiện hành động dựa trên URL của các trang cũ và trang mới.

Ví dụ: trong Trình điều hướng ngăn xếp MPA, loại ảnh động cần sử dụng sẽ phụ thuộc vào đường dẫn điều hướng:

  • Khi điều hướng từ trang tổng quan đến một trang chi tiết, nội dung mới cần phải trượt từ phải sang trái.
  • Khi điều hướng từ trang chi tiết đến trang tổng quan, nội dung cũ cần phải trượt từ trái sang phải.

Để thực hiện việc này, bạn cần thông tin về thao tác điều hướng, trong trường hợp pageswap, sắp xảy ra hoặc trong trường hợp pagereveal vừa xảy ra.

Để làm được điều này, các trình duyệt nay có thể hiển thị các đối tượng NavigationActivation chứa thông tin về cách điều hướng cùng nguồn gốc. Đối tượng này hiển thị loại điều hướng đã sử dụng, hiện tại và các mục nhập nhật ký đích cuối cùng như có trong navigation.entries() của Navigation API.

Trên một trang đã kích hoạt, bạn có thể truy cập vào đối tượng này thông qua navigation.activation. Trong sự kiện pageswap, bạn có thể truy cập vào dữ liệu này thông qua e.activation.

Hãy xem bản minh hoạ Hồ sơ này, trong đó sử dụng thông tin NavigationActivation trong các sự kiện pageswappagereveal để đặt giá trị view-transition-name trên các phần tử cần tham gia vào quá trình chuyển đổi chế độ xem.

Nhờ đó, bạn không phải trang trí trước từng mục trong danh sách bằng view-transition-name. Thay vào đó, điều này xảy ra đúng lúc bằng cách sử dụng JavaScript và chỉ trên các phần tử cần mã này.

Bản ghi bản minh hoạ Hồ sơ. Yêu cầu phải có Chrome 126 trở lên.

Sau đây là mã:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove view-transition-names after snapshots have been taken
      // (this to deal with BFCache)
      await e.viewTransition.finished;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove names after snapshots have been taken
      // so that we're ready for the next navigation
      await e.viewTransition.ready;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

Mã này cũng tự dọn dẹp bằng cách xoá các giá trị view-transition-name sau khi quá trình chuyển đổi chế độ xem chạy. Bằng cách này, trang sẵn sàng cho các thao tác điều hướng liên tiếp và cũng có thể xử lý việc truyền tải nhật ký.

Để hỗ trợ việc này, hãy sử dụng hàm tiện ích này để tạm thời đặt các view-transition-name.

const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = name;
  }

  await vtPromise;

  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = '';
  }
}

Hiện tại, bạn có thể đơn giản hoá mã trước đó như sau:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      // Clean up after the page got replaced
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.finished);
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      // Clean up after the snapshots have been taken
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.ready);
    }
  }
});

Chờ nội dung tải bằng tính năng chặn hiển thị

Hỗ trợ trình duyệt

  • Chrome: 124.
  • Cạnh: 124.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Trong một số trường hợp, bạn có thể muốn trì hoãn lần kết xuất đầu tiên trên một trang cho đến khi một phần tử nhất định hiện diện trong DOM mới. Điều này giúp tránh nhấp nháy và đảm bảo trạng thái bạn đang tạo ảnh động là ổn định.

Trong <head>, hãy xác định một hoặc nhiều mã phần tử cần có trước khi trang được kết xuất lần đầu bằng cách sử dụng thẻ meta sau.

<link rel="expect" blocking="render" href="#section1">

Thẻ meta này có nghĩa là phần tử phải có trong DOM, chứ không phải nội dung phải được tải. Ví dụ: với hình ảnh, chỉ sự hiện diện của thẻ <img> với id được chỉ định trong cây DOM là đủ để điều kiện được đánh giá là true. Hệ thống có thể vẫn đang tải hình ảnh.

Trước khi bạn tìm hiểu toàn bộ về tính năng chặn hiển thị, xin lưu ý rằng hiển thị gia tăng là một khía cạnh cơ bản của Web, vì vậy hãy thận trọng khi chọn chặn hiển thị. Tác động của việc chặn hiển thị cần được đánh giá theo từng trường hợp. Theo mặc định, hãy tránh sử dụng blocking=render trừ phi bạn có thể chủ động đo lường và đo lường mức độ tác động đối với người dùng, thông qua việc đo lường tác động đối với Các chỉ số quan trọng chính của trang web.


Xem các loại hiệu ứng chuyển đổi trong chế độ chuyển đổi giữa nhiều tài liệu

Hiệu ứng chuyển đổi chế độ xem trên nhiều tài liệu cũng hỗ trợ các loại chuyển đổi chế độ xem để tuỳ chỉnh ảnh động và thành phần được chụp.

Ví dụ: khi chuyển đến trang tiếp theo hoặc trang trước trong một chế độ phân trang, bạn có thể muốn sử dụng các ảnh động khác nhau tuỳ thuộc vào việc bạn sẽ chuyển đến trang cao hơn hay trang thấp hơn trong trình tự.

Để đặt trước các loại này, hãy thêm các loại trong quy tắc @view-transition:

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

Để thiết lập nhanh các kiểu, hãy sử dụng các sự kiện pageswappagereveal để thao tác với giá trị của e.viewTransition.types.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
    e.viewTransition.types.add(transitionType);
  }
});

Các kiểu không được tự động chuyển từ đối tượng ViewTransition trên trang cũ sang đối tượng ViewTransition của trang mới. Bạn cần xác định(các) loại cần sử dụng trên ít nhất là trang mới để ảnh động chạy như mong đợi.

Để phản hồi những loại này, hãy sử dụng bộ chọn lớp giả :active-view-transition-type() theo cách tương tự như với hiệu ứng chuyển đổi chế độ xem cùng một tài liệu

/* 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 */
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;
  }
}

Bởi vì các loại chỉ áp dụng cho chuyển đổi chế độ xem đang hoạt động, nên các loại sẽ tự động được dọn dẹp khi chuyển đổi chế độ xem kết thúc. Do đó, các kiểu sẽ hoạt động tốt với các tính năng như BFCache.

Bản minh hoạ

Trong bản minh hoạ phân trang sau, nội dung trang trượt tiến hoặc lùi dựa trên số trang mà bạn đang truy cập.

Bản ghi bản minh hoạ phân trang (MPA). Cách này sử dụng các hiệu ứng chuyển cảnh khác nhau tuỳ thuộc vào trang bạn sẽ chuyển đến.

Loại chuyển đổi cần sử dụng được xác định trong các sự kiện pagerevealpageswap bằng cách xem xét các URL đến và đi.

const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
  const currentURL = new URL(fromNavigationEntry.url);
  const destinationURL = new URL(toNavigationEntry.url);

  const currentPathname = currentURL.pathname;
  const destinationPathname = destinationURL.pathname;

  if (currentPathname === destinationPathname) {
    return "reload";
  } else {
    const currentPageIndex = extractPageIndexFromPath(currentPathname);
    const destinationPageIndex = extractPageIndexFromPath(destinationPathname);

    if (currentPageIndex > destinationPageIndex) {
      return 'backwards';
    }
    if (currentPageIndex < destinationPageIndex) {
      return 'forwards';
    }

    return 'unknown';
  }
};

Phản hồi

Chúng tôi luôn trân trọng ý kiến phản hồi của nhà phát triển. Để chia sẻ, hãy gửi vấn đề cho Nhóm hoạt động CSS trên GitHub kèm theo các đề xuất và câu hỏi. Thêm tiền tố [css-view-transitions] cho vấn đề của bạn. Nếu bạn gặp lỗi, hãy gửi lỗi Chromium.