מעבר תצוגה מתרחש בין שני מסמכים שונים נקרא מעבר לתצוגה בין מסמכים. לרוב זה המצב באפליקציות שכוללות דפים מרובים (MPA). ב-Chrome יש תמיכה במעברים בין תצוגות של מסמכים מ-Chrome 126.
תמיכה בדפדפן
מעברים בין תצוגות של מסמכים שונים מתבססים על אותם אבני בניין ועקרונות כמו מעברים בין תצוגות באותו מסמך, באופן מכוון מאוד:
- הדפדפן מצלם תמונות מצב של רכיבים בעלי
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.
בכלל @view-transition
, צריך להגדיר את המתאר navigation
כ-auto
כדי לאפשר מעברים בין מסמכים מאותו מקור בכמה מסמכים.
@view-transition {
navigation: auto;
}
הגדרת המתאר של navigation
לערך auto
מבטאת את הסכמתך לאפשר מעברים בין תצוגות מפורטות עבור NavigationTypeים הבאים:
traverse
push
אוreplace
, אם ההפעלה לא הופעלה על ידי המשתמש במנגנונים של ממשק המשתמש בדפדפן.
לדוגמה, ניווטים שלא נכללים בauto
הם ניווט באמצעות סרגל הכתובות של כתובת ה-URL או לחיצה על סימנייה, וכן כל סוג של טעינה מחדש ביוזמת המשתמש או הסקריפט.
אם הניווט נמשך יותר מ-4 שניות במקרה של דפדפן Chrome, המערכת תדלג על המעבר בין התצוגה באמצעות TimeoutError
DOMException
.
הדגמה של מעברי תצוגה בין מסמכים
עיינו בהדגמה הבאה שמשתמשת במעברים בין תצוגות כדי ליצור הדגמה של Stack Navigator. אין כאן קריאות ל-document.startViewTransition()
. מעברים בין תצוגות מופעלות על ידי ניווט מדף אחד לדף אחר.
התאמה אישית של מעברים בין תצוגות במסמכים שונים
יש כמה תכונות של פלטפורמות אינטרנט שאפשר להשתמש בהן כדי להתאים אישית מעברים בין תצוגות במסמכים שונים.
התכונות האלה לא חלק מהמפרט של View Transition API, אבל אפשר להשתמש בהן בשילוב עם הממשק הזה.
האירועים pageswap
ו-pagereveal
כדי לאפשר לכם להתאים אישית מעברים בין תצוגות במסמכים שונים, מפרט ה-HTML כולל שני אירועים חדשים שאפשר להשתמש בהם: pageswap
ו-pagereveal
.
שני האירועים האלה מופעלים בכל ניווט בין מסמכים מאותו מקור, גם אם המעבר לתצוגה עומד להתרחש וגם אם לא. אם מעבר של תצוגה מפורטת עומד להתרחש בין שני הדפים, תוכלו לגשת לאובייקט ViewTransition
באמצעות המאפיין viewTransition
באירועים האלה.
- האירוע
pageswap
מופעל לפני העיבוד של הפריים האחרון בדף. תוכלו להשתמש בה כדי לבצע שינויים ברגע האחרון בדף היוצא, ממש לפני הצילום של תמונות המצב הישנות. - האירוע
pagereveal
מופעל בדף אחרי שהוא הופעל או הופעל מחדש, אבל לפני הזדמנות הרינדור הראשונה. בעזרתה תוכלו להתאים אישית את הדף החדש לפני שהתמונות יצולמו.
לדוגמה, אפשר להשתמש באירועים האלה כדי להגדיר או לשנות במהירות ערכי 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
.
כדאי לעיין בהדגמה הזו של פרופילים, שבה נעשה שימוש במידע מסוג 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 מספיקה כדי שהתנאי יקבל את הערך True. התמונה עצמה עדיין נטענת.
לפני שמתחילים להשתמש בחסימת עיבודים, חשוב לזכור שרינדור מצטבר הוא היבט בסיסי באינטרנט. לכן חשוב להיזהר כשבוחרים לחסום את העיבוד. צריך להעריך את ההשפעה של רינדור החסימה על בסיס כל מקרה לגופו. כברירת מחדל, מומלץ להימנע משימוש ב-blocking=render
, אלא אם אפשר למדוד באופן פעיל את ההשפעה על המשתמשים באמצעות מדידת ההשפעה של מדדי הליבה לבדיקת חוויית המשתמש באתר.
הצגה של סוגי מעברים בין תצוגות של מסמכים
מעברים בין תצוגות מסמכים תומכים גם בסוגי מעברים של תצוגה כדי להתאים אישית את האנימציות ולקבוע אילו רכיבים יצולמו.
לדוגמה, כשעוברים לדף הבא או לדף הקודם בעימוד, ייתכן שתרצו להשתמש באנימציות שונות, בהתאם למעבר לדף הגבוה יותר או לדף הנמוך יותר ברצף.
כדי להגדיר את הסוגים האלה מראש, צריך להוסיף את הסוגים בכלל @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 במקום זאת.