Khi quá trình 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 các tài liệu. Đây thường là trường hợp trong các ứng dụng nhiều trang (MPA). Hiệu ứng chuyển đổi chế độ xem giữa các tài liệu được hỗ trợ trong Chrome từ phiên bản 126.
Hỗ trợ trình duyệt
Các hiệu ứng chuyển đổi chế độ xem giữa các tài liệu dựa trên các khối và nguyên tắc giống hệt như các hiệu ứng chuyển đổi chế độ xem trong cùng một tài liệu, điều này rất có 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.
- Cuối cùng, hiệu ứng chuyển đổi được cung cấp bởi ả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 trong cùng một tài liệu là với các 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 hiệu ứng chuyển đổi chế độ xem. Thay vào đó, điều kiện kích hoạt cho quá trình 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 từ trang này sang trang khác, một thao tác mà người dùng trang web của bạn thường thực hiện bằng cách nhấp vào một đường liên kết.
Nói cách khác, không có API nào để gọi nhằm bắt đầu quá trình chuyển đổi chế độ xem giữa hai tài liệu. Tuy nhiên, bạn cần đáp ứng hai điều kiện sau:
- Cả hai tài liệu cần tồn tại trên cùng một nguồn gốc.
- Cả hai trang đều cần chọn tham gia để cho phép chuyển đổi chế độ xem.
Cả hai điều kiện này đều được giải thích ở phần sau của tài liệu này.
Các hiệu ứng chuyển đổi khung hiển thị giữa các tài liệu chỉ được giới hạn ở các thao tác điều hướng có cùng nguồn gốc
Hoạt động chuyển đổi chế độ xem giữa các tài liệu chỉ được giới hạn ở các thao tác điều hướng cùng nguồn gốc. Một thao tác điều hướng được coi là 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 của lược đồ, tên máy chủ và cổng được sử dụng, như thông tin chi tiết trên web.dev.
Ví dụ: bạn có thể có quá trình 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 tài liệu đó có cùng nguồn gốc.
Bạn không thể thực hiện quá trình chuyển đổi đó khi di chuyển từ developer.chrome.com
đến www.chrome.com
, vì đó là các trang web khác nguồn gốc và cùng trang web.
Bạn có thể chọn sử dụng tính năng chuyển đổi chế độ xem giữa các tài liệu
Để có hiệu ứng chuyển đổi chế độ xem giữa hai tài liệu, cả hai trang tham gia đều phải chọn cho phép hiệu ứng này. Bạn có thể thực hiện việc này bằng quy tắc at-rule @view-transition
trong CSS.
Trong quy tắc at-rule @view-transition
, hãy đặt chỉ số mô tả navigation
thành auto
để bật hiệu ứng chuyển đổi chế độ xem cho các thao tác điều hướng cùng nguồn gố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 chọn cho phép chuyển đổi chế độ xem xảy 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ác cơ chế giao diện người dùng của trình duyệt.
Ví dụ: các thao tác điều hướng bị loại trừ khỏi auto
là thao tác điều hướng bằng thanh địa chỉ URL hoặc nhấp vào một dấu trang, cũng như mọi hình thức tải lại do người dùng hoặc tập lệnh khởi tạo.
Nếu quá trình đ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ì quá trình chuyển đổi chế độ xem sẽ bị bỏ qua bằng TimeoutError
DOMException
.
Bản minh hoạ chuyển đổi chế độ xem giữa các tài liệu
Hãy xem bản minh hoạ sau đây sử dụng hiệu ứng chuyển đổi thành phần hiển thị để tạo bản minh hoạ về Trình điều hướng ngăn xếp. Không có lệnh gọi nào đến document.startViewTransition()
ở đây, các hiệu ứng chuyển đổi thành phần hiển thị được kích hoạt bằng cách di chuyển từ trang này sang trang khác.
Tuỳ chỉnh hiệu ứng chuyển đổi chế độ xem giữa các tài liệu
Để tuỳ chỉnh hiệu ứng chuyển đổi chế độ xem giữa các tài liệu, bạn có thể sử 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 phải là một phần của bản đặc tả API Chuyển đổi khung hiển thị, nhưng được thiết kế để sử dụng cùng với API này.
Sự kiện pageswap
và pagereveal
Để cho phép bạn tuỳ chỉnh các hiệu ứng chuyển đổi chế độ xem giữa các tài liệu, quy cách 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 thao tác điều hướng giữa các tài liệu có cùng nguồn gốc, bất kể quá trình chuyển đổi chế độ xem sắp diễn ra hay không. Nếu một lượt chuyển đổi thành phần hiển thị sắp diễn 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
sẽ 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 cuối trên trang sắp chuyển đổi, ngay trước khi ảnh chụp nhanh cũ được chụp. - 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. Với tính năng này, bạn có thể tuỳ chỉnh trang mới trước khi chụp ảnh tổng quan 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 quá trình chuyển đổi chế độ xem trước khi quá trình này 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à hai đối tượng khác nhau. Các phương thức này 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ượngViewTransition
cũ sẽ bị bỏ qua. Khi đó,viewTransition.ready
sẽ từ chối vàviewTransition.finished
sẽ phân giải.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 các lời hứaviewTransition.ready
vàviewTransition.finished
.
Thông tin kích hoạt tính năng chỉ đường
Trong cả sự kiện pageswap
và pagereveal
, bạn cũng có thể hành động dựa trên URL của trang cũ và 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 phụ thuộc vào đường dẫn điều hướng:
- Khi chuyển từ trang tổng quan sang trang chi tiết, nội dung mới cần trượt từ phải sang trái.
- Khi chuyển từ trang chi tiết sang trang tổng quan, nội dung cũ cần trượt từ trái sang phải.
Để làm việc này, bạn cần có thông tin về thao tác điều hướng sắp diễn ra (trong trường hợp pageswap
) hoặc vừa diễn ra (trong trường hợp pagereveal
).
Do đó, trình duyệt hiện có thể hiển thị các đối tượng NavigationActivation
chứa thông tin về thao tác đ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, mục hiện tại và mục nhật ký đích đến cuối cùng như trong navigation.entries()
từ API Điều hướng.
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 thông tin này thông qua e.activation
.
Hãy xem bản minh hoạ Hồ sơ này 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 chuyển đổi chế độ xem.
Bằng cách đó, bạn không cần phải trang trí trước view-transition-name
cho từng mục trong danh sách. Thay vào đó, việc này sẽ xảy ra đúng lúc 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 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 cập vào 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 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 = '';
}
}
Giờ đây, 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
Trong một số trường hợp, bạn có thể muốn tạm hoãn quá trình kết xuất đầu tiên của một 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 hiện tượng 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 hiển thị lần đầu tiên 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ỉ cần có thẻ <img>
với id
được chỉ định trong cây DOM là đủ để điều kiện đánh giá là đúng. Hình ảnh đó vẫn có thể đang tải.
Trước khi bạn sử dụng tính năng chặn kết xuất, hãy lưu ý rằng tính năng kết xuất tăng dần 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 kết xuất. Bạn cần đánh giá tác động của việc chặn kết xuất 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 blocking=render
đối với người dùng bằng cách đo lường tác động của blocking=render
đối với Các chỉ số quan trọng về trang web.
Xem các loại chuyển đổi trong chuyển đổi chế độ xem giữa các tài liệu
Các hiệu ứng chuyển đổi chế độ xem trên nhiều tài liệu cũng hỗ trợ các loại hiệu ứng chuyển đổi chế độ xem để tuỳ chỉnh ảnh động và các phần tử đượ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 các ảnh động khác nhau tuỳ thuộc vào việc bạn đang 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 tại @view-transition
:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Để thiết lập các loại ngay lập tức, hãy sử dụng các 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 tự động được 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 để sử dụng trên ít nhất trang mới để ả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 hiệu ứng chuyển đổi chế độ xem trong 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;
}
}
Vì các loại chỉ áp dụng cho một lượt chuyển đổi thành phần hiển thị đang hoạt động, nên các loại sẽ tự động được dọn dẹp khi lượt chuyển đổi thành phần hiển thị kết thúc. Do đó, các loại 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ề trước hoặc về sau dựa trên số trang mà bạn đang chuyển đến.
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 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 làm việc về CSS trên GitHub kèm theo các đề xuất và câu hỏi. Đặt 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.