כשמעבר תצוגה מתרחש בין שני מסמכים שונים, הוא נקרא מעבר תצוגה בין מסמכים. המצב הזה נפוץ בדרך כלל באפליקציות עם כמה דפים (MPA). אפשר לבצע מעברים בין תצוגות במסמכים שונים ב-Chrome מגרסה 126 ואילך.
תמיכה בדפדפנים
מעברים בין תצוגות במסמכים שונים מבוססים על אותם אבני בניין ועקרונות כמו מעברים בין תצוגות באותו מסמך, וזו מטרה מכוונת:
- הדפדפן יוצר קובצי snapshot של אלמנטים שיש להם
view-transition-name
ייחודי גם בדף הישן וגם בדף החדש. - ה-DOM מתעדכן בזמן שהעיבוד מושעה.
- ולבסוף, המעברים מבוססים על אנימציות CSS.
ההבדל בהשוואה למעברים בין תצוגות באותו מסמך הוא שאין צורך לבצע קריאה ל-document.startViewTransition
כדי להתחיל מעבר בין תצוגות שונות. במקום זאת, הטריגר למעבר בין תצוגות במסמכים שונים הוא ניווט מאותו מקור מדף אחד לדף אחר, פעולה שמשתמש באתר שלכם מבצע בדרך כלל על ידי לחיצה על קישור.
במילים אחרות, אין API שאפשר לקרוא אליו כדי להתחיל מעבר תצוגה בין שני מסמכים. עם זאת, יש שני תנאים שצריך לעמוד בהם:
- שני המסמכים צריכים להתקיים באותו מקור.
- צריך להביע הסכמה בשני הדפים כדי לאפשר את המעבר בין התצוגות.
שני התנאים האלה מוסברים בהמשך המאמר.
מעברים בין תצוגות במסמכים שונים מוגבלים לניווטים מאותו מקור
מעברים בין תצוגות במסמכים שונים מוגבלים לניווטים מאותו מקור בלבד. ניווט נחשב כניווט מאותו מקור אם המקור של שני הדפים המעורבים זהה.
מקור הדף הוא שילוב של הסכמה, שם המארח והיציאה, כפי שמפורט ב-web.dev.
לדוגמה, אפשר להשתמש במעבר בין תצוגות במסמכים שונים כשעוברים מ-developer.chrome.com
אל developer.chrome.com/blog
, כי מדובר באותו מקור.
אי אפשר לבצע את המעבר הזה כשמנווטים מ-developer.chrome.com
אל www.chrome.com
, כי מדובר במקורות שונים ובאותו אתר.
כדי להשתמש במעברים בין תצוגות במסמכים שונים, צריך להביע הסכמה
כדי לאפשר מעבר בין שני מסמכים, צריך להביע הסכמה בשני הדפים המשתתפים. כדי לעשות זאת, משתמשים בכלל @view-transition
ב-CSS.
בכלל at-rule @view-transition
, מגדירים את המאפיין navigation
לערך auto
כדי להפעיל מעברים בין תצוגות בניווטים במסמכים שונים מאותו מקור.
@view-transition {
navigation: auto;
}
הגדרת המאפיין navigation
לערך auto
מאפשרת מעבר בין תצוגות עבור NavigationType:
traverse
push
אוreplace
, אם ההפעלה לא בוצעה על ידי המשתמש באמצעות מנגנונים של ממשק המשתמש בדפדפן.
ניווטים שלא נכללים ב-auto
הם, למשל, ניווט באמצעות סרגל הכתובות של כתובת ה-URL או לחיצה על סימנייה, וכן כל סוג של טעינת חוזרת שהמשתמש או סקריפט יזמו.
אם הניווט נמשך יותר מדי זמן – יותר מארבע שניות ב-Chrome – מעבר התצוגה ינוข้าม באמצעות TimeoutError
DOMException
.
הדגמה של מעברים בין תצוגות במסמכים שונים
עיינו בהדגמה הבאה שמשתמשת במעברים בין תצוגות כדי ליצור הדגמה של Stack Navigator. אין כאן קריאות ל-document.startViewTransition()
. מעברים בין תצוגות מופעלות על ידי ניווט מדף אחד לדף אחר.
התאמה אישית של מעברים בין תצוגות במסמכים שונים
כדי להתאים אישית את מעברי התצוגה בין מסמכים, יש כמה תכונות של פלטפורמת האינטרנט שאפשר להשתמש בהן.
התכונות האלה לא חלק מהמפרט של View Transition API, אבל אפשר להשתמש בהן בשילוב עם הממשק הזה.
האירועים pageswap
ו-pagereveal
כדי לאפשר לכם להתאים אישית מעברים בין תצוגות במסמכים שונים, מפרט ה-HTML כולל שני אירועים חדשים שאפשר להשתמש בהם: pageswap
ו-pagereveal
.
שני האירועים האלה מופעלים בכל ניווט בין מסמכים מאותו מקור, גם אם המעבר לתצוגה עומד להתרחש וגם אם לא. אם מעבר תצוגה עומד להתרחש בין שני הדפים, תוכלו לגשת לאובייקט ViewTransition
באמצעות המאפיין viewTransition
באירועים האלה.
- האירוע
pageswap
מופעל לפני שפריימים האחרונים של דף מסוים עוברים עיבוד. אפשר להשתמש באפשרות הזו כדי לבצע שינויים ברגע האחרון בדף היוצא, ממש לפני שצולמו קובצי ה-snapshot הישנים. - האירוע
pagereveal
מופעל בדף אחרי שהוא הופעל או הופעל מחדש, אבל לפני הזדמנות הרינדור הראשונה. בעזרתו תוכלו להתאים אישית את הדף החדש לפני יצירת קובצי ה-snapshot החדשים.
לדוגמה, אפשר להשתמש באירועים האלה כדי להגדיר או לשנות במהירות ערכים מסוימים של view-transition-name
, או להעביר נתונים ממסמך אחד למסמך אחר על ידי כתיבת נתונים מ-sessionStorage
וקריאת נתונים ממנו, כדי להתאים אישית את המעבר בין התצוגות לפני שהוא פועל בפועל.
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');
}
});
אם רוצים, אפשר לדלג על המעבר בשני האירועים.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
האובייקט ViewTransition
ב-pageswap
וב-pagereveal
הם שני אובייקטים שונים. בנוסף, הן מטפלות בהבטחות שונות באופן שונה:
pageswap
: אחרי שהמסמך מוסתר, האובייקט הישן שלViewTransition
ינוข้าม. במקרה כזה,viewTransition.ready
דוחה את הבקשה ו-viewTransition.finished
פותר אותה.pagereveal
: ההבטחהupdateCallBack
כבר טופלה בשלב הזה. אפשר להשתמש בהבטחותviewTransition.ready
ו-viewTransition.finished
.
מידע על הפעלת הניווט
גם באירועים pageswap
וגם באירועים pagereveal
, אפשר לבצע פעולה על סמך כתובות ה-URL של הדפים הישנים והחדשים.
לדוגמה, ב-MPA Stack Navigator, סוג האנימציה שבו משתמשים תלוי בנתיב הניווט:
- כשעוברים מדף הסקירה הכללית לדף פרטים, התוכן החדש צריך להחליק מימין לשמאל.
- כשעוברים מדף הפרטים לדף הסקירה הכללית, התוכן הישן צריך להחליק החוצה משמאל לימין.
לשם כך נדרש מידע על הניווט שעומד להתרחש, במקרה של pageswap
, או במקרה של pagereveal
שהתרחש.
לשם כך, הדפדפנים יכולים עכשיו לחשוף אובייקטים מסוג NavigationActivation
שמכילים מידע על הניווט מאותו מקור. האובייקט הזה חושף את סוג הניווט שבו נעשה שימוש, את הרשומות הנוכחיות ואת הרשומות של היסטוריית היעד הסופית כפי שהן נמצאות ב-navigation.entries()
מ-Navigation API.
בדף מופעל, אפשר לגשת לאובייקט הזה דרך navigation.activation
. באירוע pageswap
, אפשר לגשת לנתונים האלה דרך e.activation
.
כדאי לעיין בהדגמה הזו של Profiles, שבה נעשה שימוש במידע NavigationActivation
באירועים pageswap
ו-pagereveal
כדי להגדיר את ערכי view-transition-name
ברכיבים שצריכים להשתתף במעבר התצוגה.
כך לא צריך לקשט כל פריט ברשימה באמצעות view-transition-name
מראש. במקום זאת, הפעולה מתבצעת בדיוק בזמן באמצעות JavaScript, רק ברכיבים שזקוקים לו.
הקוד הוא:
// 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';
}
}
});
הקוד גם מנקה אחרי עצמו על ידי הסרת הערכים של view-transition-name
אחרי שהמעבר בין התצוגות הופעל. כך הדף מוכן לניווטים עוקבים ויכול גם לטפל בסריקה של ההיסטוריה.
כדי לעזור בכך, אפשר להשתמש בפונקציית השירות הזו שמגדירה את הערכים של 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 = '';
}
}
עכשיו אפשר לפשט את הקוד הקודם באופן הבא:
// 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);
}
}
});
המתנה לטעינת התוכן באמצעות חסימה של עיבוד
תמיכה בדפדפנים
במקרים מסוימים, כדאי להשהות את העיבוד הראשון של דף עד שרכיב מסוים יופיע ב-DOM החדש. כך ניתן למנוע את הבהוב ולוודא שהמצב שבו מתבצעת האנימציה יציב.
ב-<head>
, מגדירים מזהה רכיב אחד או יותר שצריך להתקיים לפני שהדף מקבל את העיבוד הראשון, באמצעות המטא תג הבא.
<link rel="expect" blocking="render" href="#section1">
מטא התג הזה מציין שהרכיב צריך להופיע ב-DOM, ולא שהתוכן צריך להיטען. לדוגמה, בתמונות, נוכחות התג <img>
עם הערך שצוין של id
בעץ ה-DOM מספיקה כדי שהתנאי יהיה נכון. יכול להיות שהתמונה עצמה עדיין נטענת.
לפני שמשתמשים באופן מלא בחסימת עיבוד, חשוב לזכור שעיבוד מצטבר הוא היבט בסיסי של האינטרנט, ולכן צריך להפעיל שיקול דעת כשמחליטים לחסום את העיבוד. צריך להעריך את ההשפעה של רינדור החסימה על בסיס כל מקרה לגופו. כברירת מחדל, מומלץ להימנע משימוש ב-blocking=render
אלא אם יש לכם אפשרות למדוד ולבחון באופן פעיל את ההשפעה שלו על המשתמשים, על ידי מדידת ההשפעה על המדדים הבסיסיים של חוויית המשתמש (Core Web Vitals).
הצגה של סוגי מעברים בין תצוגות של מסמכים
מעברים בין תצוגות במסמכים שונים תומכים גם בסוגים של מעברים בין תצוגות, כדי להתאים אישית את האנימציות ואת הרכיבים שייכללו בצילום.
לדוגמה, כשעוברים לדף הבא או לדף הקודם בחלוקה לדפים, כדאי להשתמש באנימציות שונות בהתאם לכך שעוברים לדף גבוה יותר או לדף נמוך יותר ברצף.
כדי להגדיר את הסוגים האלה מראש, צריך להוסיף את הסוגים בכלל @view-transition
בכתובת ה-URL:
@view-transition {
navigation: auto;
types: slide, forwards;
}
כדי להגדיר את הסוגים במהירות, צריך להשתמש באירועים pageswap
ו-pagereveal
כדי לשנות את הערך של 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);
}
});
הסוגים לא מועברים באופן אוטומטי מהאובייקט ViewTransition
בדף הישן לאובייקט ViewTransition
בדף החדש. כדי שהאנימציות יפעלו כצפוי, צריך לקבוע את הסוגים שבהם רוצים להשתמש לפחות בדף החדש.
כדי להגיב לסוגים האלה, משתמשים בבורר של פסאודו-הקלאס :active-view-transition-type()
באותו אופן שבו משתמשים בו במעברים בין תצוגות באותו מסמך.
/* 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;
}
}
מאחר שהסוגים חלים רק על מעבר פעיל של תצוגה, הסוגים נמחקים באופן אוטומטי בסיום מעבר התצוגה. לכן, סוגים פועלים היטב עם תכונות כמו BFCache.
הדגמה (דמו)
בהדגמה הבאה של חלוקה לדפים, תוכן הדף מחליק קדימה או אחורה בהתאם למספר הדף שאליו אתם מנווטים.
סוג המעבר שבו צריך להשתמש נקבע באירועים pagereveal
ו-pageswap
על סמך כתובות ה-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';
}
};
משוב
אנחנו תמיד שמחים לקבל משוב ממפתחים. כדי לשתף, שולחים דיווח על בעיה לקבוצת העבודה של שירותי ה-CSS ב-GitHub עם הצעות ושאלות. מוסיפים את הקידומת [css-view-transitions]
לבעיה.
אם נתקלתם בבאג, תוכלו לדווח על באג ב-Chromium במקום זאת.