Khi quá trình chuyển đổi thành phần hiển thị 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 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). Chrome 126 hỗ trợ chuyển đổi chế độ xem nhiều tài liệu.
Chuyển đổi chế độ xem nhiều tài liệu dựa trên cùng một khối dựng và nguyên tắc như chuyển đổi chế độ xem cùng một tài liệu, rất có chủ đích:
- Trình duyệt chụp nhanh các phần tử có
view-transition-name
duy nhất trên cả trang cũ và trang mới. - DOM được cập nhật trong khi quá trình kết xuất bị chặn.
- Và cuối cùng, hiệu ứng chuyển đổi được hỗ trợ bởi ảnh động CSS.
Điểm khác biệt khi so sánh với các lượt chuyển đổi chế độ xem cùng tài liệu là với các lượt chuyển đổi chế độ xem 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 đó, yếu tố kích hoạt cho việc chuyển đổi chế độ xem nhiều tài liệu là việc điều hướng cùng nguồn gốc từ trang này sang trang khác, một hành động thường do người dùng trang web của bạn nhấp vào đường liên kết thực hiện.
Nói cách khác, không có API nào cần gọi để bắt đầu quá trình chuyển đổi thành phần hiển thị giữa hai tài liệu. Tuy nhiên, có 2 điều kiện cần được đáp ứng:
- Cả hai tài liệu cần phải tồn tại trên cùng một nguồn gốc.
- Cả hai trang đều cần chọn sử dụng để cho phép chuyển đổi chế độ xem.
Cả hai điều kiện này sẽ được giải thích ở phần sau của tài liệu này.
Hoạt động chuyển đổi chế độ xem nhiều tài liệu bị giới hạn trong các lần điều hướng cùng nguồn gốc
Hoạt động chuyển đổi chế độ xem nhiều tài liệu chỉ bị giới hạn trong các thao tác điều hướng cùng nguồn gốc. Một điều hướng được xem là có cùng nguồn gốc nếu nguồn gốc của cả hai trang tham gia là giống nhau.
Nguồn gốc của một trang là sự kết hợp củ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.
Ví dụ: bạn có thể chuyển đổi chế độ xem nhiều tài liệu khi điều hướng từ developer.chrome.com
sang developer.chrome.com/blog
, vì các chế độ xem đó có cùng nguồn gốc.
Bạn không thể thực hiện hiệu ứng chuyển đổi đó khi điều hướng từ developer.chrome.com
sang www.chrome.com
, vì đó là các hoạt động trên nhiều nguồn gốc và trên cùng một trang web.
Chọn sử dụng chuyển đổi chế độ xem nhiều tài liệu
Để chuyển đổi chế độ xem nhiều tài liệu giữa hai tài liệu, cả hai trang tham gia đều cần chọn cho phép việc này. Bạn có thể thực hiện việc này 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 hoạt động điều hướng cùng nguồn gốc, trên nhiều tài liệu.
@view-transition {
navigation: auto;
}
Khi đặt chỉ số mô tả navigation
thành auto
, bạn chọn cho phép quá trình chuyển đổi khung hiển thị diễn ra cho các NavigationType sau:
traverse
push
hoặcreplace
, nếu người dùng không kích hoạt thông qua cơ chế giao diện người dùng của trình duyệt.
Các thao tác điều hướng bị loại trừ khỏi auto
là điều hướng bằng cách sử dụng thanh địa chỉ URL hoặc nhấp vào dấu trang, cũng như bất kỳ hình thức người dùng hoặc tập lệnh nào bắt đầu tải lại.
Nếu một thao tác điều hướng mất quá nhiều thời gian (hơn 4 giây trong trường hợp của Chrome), thì hiệu ứng chuyển đổi khung hiển thị sẽ bị bỏ qua bằng TimeoutError
DOMException
.
Bản minh hoạ chuyển đổi chế độ xem nhiều tài liệu
Hãy xem bản minh hoạ sau đây về cách sử dụng hiệu ứng chuyển đổi khung hiển thị để tạo bản minh hoạ Stack Navigator (Trình điều hướng ngăn xếp). Không có lệnh gọi đến document.startViewTransition()
ở đây, quá trình chuyển đổi thành phần hiển thị được kích hoạt bằng cách điều hướng từ trang này sang trang khác.
Tuỳ chỉnh hiệu ứng chuyển đổi chế độ xem nhiều tài liệu
Để tuỳ chỉnh hiệu ứng chuyển đổi chế độ xem nhiều tài liệu, bạn có thể sử dụng một số tính năng trên nền tảng web.
Các tính năng này không thuộc chính quy cách của View Transition API, nhưng được thiết kế để sử dụng cùng với thông số kỹ thuật đó.
Sự kiện pageswap
và pagereveal
Để cho phép bạn tuỳ chỉnh các hiệu ứng chuyển đổi chế độ xem nhiều tài liệu, thông số kỹ thuật HTML bao gồm hai sự kiện mới mà bạn có thể sử dụng: pageswap
và pagereveal
.
Hai sự kiện này được kích hoạt cho mọi hoạt động điều hướng trên nhiều tài liệu có cùng nguồn gốc bất kể quá trình chuyển đổi chế độ xem có sắp xảy ra hay không. Nếu quá trình chuyển đổi khung hiển thị sắp xảy ra giữa hai trang này, 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
sẽ kích hoạt trước khi khung hình cuối cùng của trang 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 chụp ảnh chụp nhanh cũ. - Sự kiện
pagereveal
sẽ kích hoạt trên một trang sau khi trang đó được khởi chạy hoặc kích hoạt lại, nhưng trước cơ hội kết xuất đầu tiên. Nhờ công cụ này, bạn có thể tuỳ chỉnh trang mới trước khi chụp ảnh nhanh mới.
Ví dụ: bạn có thể sử dụng các sự kiện này để nhanh chóng đặt hoặc thay đổi 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 chế độ xem trước khi nó thực sự chạy.
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 quá trình 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 pageswap
và pagereveal
là 2 đối tượng khác nhau. Họ cũng xử lý nhiều hứa hẹn theo cách khác nhau:
pageswap
: Sau khi tài liệu bị ẩn, đối tượngViewTransition
cũ sẽ bị bỏ qua. Khi điều đó xảy ra,viewTransition.ready
sẽ từ chối vàviewTransition.finished
sẽ giải quyết.pagereveal
: Lời hứaupdateCallBack
đã được giải quyết tại thời điểm này. Bạn có thể sử dụng lời hứaviewTransition.ready
vàviewTransition.finished
.
Thông tin kích hoạt tính năng đi theo chỉ dẫn
Trong cả hai sự kiện pageswap
và pagereveal
, 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à mới.
Ví dụ: trong MPA Stack Navigator (Trình điều hướng ngăn xếp MPA), loại ảnh động cần sử dụng 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 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 ra từ trái sang phải.
Để thực hiện việc này, bạn cần thông tin về quá trình đ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 hiện có thể hiển thị các đối tượng NavigationActivation
chứa thông tin về hoạt động điều hướng có cùng nguồn gốc. Đối tượng này hiển thị loại điều hướng đã dùng, mục nhập nhật ký đích đến hiện tại và cuối cùng như được tìm thấy trong navigation.entries()
từ 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 phần này thông qua e.activation
.
Hãy xem Bản minh hoạ Hồ sơ này có sử dụng thông tin NavigationActivation
trong các sự kiện pageswap
và pagereveal
để đặ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.
Theo đó, bạn không phải trang trí từng mục trong danh sách bằng phần tử view-transition-name
xuất hiện trước. Thay vào đó, điều này xảy ra đúng thời điểm bằng cách sử dụng JavaScript, chỉ trên các phần tử cần JavaScript.
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 chạy hiệu ứng chuyển đổi khung hiển thị. Bằng cách này, trang sẵn sàng để điều hướng liên tiếp và cũng có thể xử lý việc truyền tải lịch sử.
Để hỗ trợ điều này, hãy dùng hàm hiệu dụng này để tạm thời đặt 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 = '';
}
}
Mã trước đây hiện có thể được đơn giản hoá 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ị
Trong một số trường hợp, bạn có thể tạm dừng lần hiển thị đầu tiên của trang cho đến khi một phần tử nhất định xuất hiện trong DOM mới. Điều này giúp tránh cài đặt ROM và đảm bảo trạng thái bạn đang tạo ảnh động ổn định.
Trong <head>
, hãy dùng thẻ meta sau để 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 tiên.
<link rel="expect" blocking="render" href="#section1">
Thẻ meta này có nghĩa là phần tử phải hiện diện trong DOM chứ không phải là nội dung phải được tải. Ví dụ: với hình ảnh, chỉ cần sự hiện diện của thẻ <img>
cùng với id
được chỉ định trong cây DOM là đủ để điều kiện được đánh giá là true (đúng). Bản thân hình ảnh đó vẫn có thể đang tải.
Trước khi sử dụng tính năng chặn hiển thị, hãy 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ị. Cần đánh giá tác động của việc chặn kết xuất hình ảnh 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à đánh giá tác động của dữ liệu đó đến người dùng bằng cách đo lường tác động đến các Các chỉ số quan trọng về trang web.
Xem các loại chuyển đổi trong quá trình chuyển đổi chế độ xem nhiều tài liệu
Hiệu ứng chuyển đổi chế độ xem 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à phần tử nào được chụp.
Ví dụ: khi chuyển đến trang tiếp theo hoặc trang trước trong một quá trình phân trang, bạn có thể muốn sử dụng nhiều ảnh động 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 vào quy tắc @view-transition
:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Để đặt loại nhanh, hãy sử dụng sự kiện pageswap
và pagereveal
để 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 loại này 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 để các ảnh động chạy như mong đợi.
Để phản hồi các 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 các chuyển đổi chế độ xem cùng 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;
}
}
Vì các kiểu chỉ áp dụng cho quá trình chuyển đổi khung hiển thị đang hoạt động nên các kiểu sẽ tự động được xoá khi quá trình chuyển đổi khung hiển thị kết thúc. Do đó, các kiểu 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 đây, nội dung trang sẽ trượt về phía trước hoặc phía sau dựa trên số trang mà bạn đang truy cập.
Loại chuyển đổi cần sử dụng được xác định trong các sự kiện pagereveal
và pageswap
bằng cách xem xét URL đến và từ URL.
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';
}
};
Ý kiến 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 đề với Nhóm hoạt động CSS trên GitHub kèm theo đề xuất và câu hỏi. Thêm tiền tố [css-view-transitions]
vào vấn đề của bạn.
Nếu bạn gặp lỗi thì hãy gửi lỗi Chromium.