מעברים בין תצוגות של מסמכים שונים באפליקציות עם מספר דפים

כאשר מעבר התצוגה מתרחש בין שני מסמכים שונים, המעבר הזה נקרא מעבר בין תצוגות של מסמכים שונים. זה המצב בדרך כלל באפליקציות מרובות דפים (MPA). מעברים בין תצוגות של מסמכים שונים נתמכים ב-Chrome מגרסה 126.

תמיכה בדפדפן

  • 126
  • 126
  • x
  • x

מקור

מעברים בין תצוגות של מסמכים שונים מסתמכים על אותם אבני בניין ועקרונות כמו מעברים של צפייה באותו מסמך, באופן מכוון מאוד:

  1. הדפדפן מצלם תמונות מצב של רכיבים שיש להם view-transition-name ייחודי בדף הישן וגם בדף החדש.
  2. ה-DOM מתעדכן בזמן שהעיבוד מבוטל.
  3. ולבסוף, המעברים מופעלים על ידי אנימציות CSS.

ההבדל בהשוואה למעברים בין תצוגות של מסמכים שונים הוא שבמעברים בין תצוגות של מסמכים שונים, אין צורך לקרוא לפונקציה document.startViewTransition כדי להתחיל במעבר בין תצוגות. במקום זאת, הטריגר למעבר בין תצוגות של מסמכים שונים הוא ניווט באותו מקור מדף אחד לדף אחר. פעולה כזו בדרך כלל מבצעת על ידי לחיצה על קישור באתר.

במילים אחרות, אין ממשק API שניתן לקרוא לו כדי להתחיל במעבר תצוגה בין שני מסמכים. עם זאת, יש שני תנאים שצריך לעמוד בהם:

  • שני המסמכים צריכים להתקיים באותו מקור.
  • שני הדפים צריכים להביע הסכמה כדי לאפשר את מעבר התצוגות.

שני התנאים האלה מוסברים בהמשך מסמך זה.


מעברים בין תצוגות של מסמכים שונים מוגבלים לניווטים ממקורות זהים

מעברים בין תצוגות של מסמכים מוגבלים לניווטים מאותו מקור בלבד. ניווט נחשב לאותו מקור אם המקור של שני הדפים המשתתפים הוא זהה.

מקור הדף הוא שילוב של הסכמה, שם מארח ויציאה, כפי שמפורט ב-web.dev.

כתובת URL לדוגמה שבה הסכמה, שם המארח והיציאה מודגשים. יחד הם יוצרים את המקור.
כתובת URL לדוגמה עם הדגשה של הסכמה, שם המארח והיציאה. יחד הם יוצרים את המקור.

לדוגמה, אפשר לקבל מעבר לתצוגה מפורטת של מסמכים שונים כשמנווטים מ-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 – המערכת תדלג על מעבר התצוגה באמצעות DOMException TimeoutError.

הדגמה של מעברים בין תצוגות של מסמכים שונים

צפה בהדגמה הבאה שמשתמשת במעברי תצוגה כדי ליצור הדגמת Stack Navigator. אין כאן שיחות אל document.startViewTransition(). המעברים בין הצפיות מופעלים על ידי ניווט מדף אחד לאחר.

הקלטה של הדגמת ה-Stack Navigator. נדרש Chrome 126 ואילך.

התאמה אישית של מעברים בין תצוגות של מסמכים שונים

כדי להתאים אישית את המעברים בין תצוגות של מסמכים שונים, יש כמה תכונות של פלטפורמות אינטרנט שאפשר להשתמש בהן.

התכונות האלו אינן חלק מהמפרט של View Transition API עצמו, אבל אפשר להשתמש בהן בשילוב איתו.

האירועים pageswap ו-pagereveal

תמיכה בדפדפן

  • 124
  • 124
  • x
  • x

מקור

כדי לאפשר לכם להתאים אישית מעברים בין תצוגות של מסמכים שונים, מפרט ה-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.

תמיכה בדפדפן

  • 123
  • 123
  • x
  • x

מקור

גם באירועים pageswap וגם באירועים pagereveal אפשר לנקוט פעולה על סמך כתובות ה-URL של הדף הישן והחדש.

לדוגמה, ב-MPA Stack Navigator, סוג האנימציה שבו צריך להשתמש תלוי בנתיב הניווט:

  • כשעוברים מדף הסקירה הכללית לדף פרטים, התוכן החדש צריך להחליק פנימה משמאל לימין.
  • כשעוברים מדף הפרטים לדף הסקירה הכללית, התוכן הישן צריך להחליק החוצה משמאל לימין.

כדי לעשות זאת, אתם צריכים מידע על הניווט שעומד להתרחש או במקרה של pagereveal, במקרה של pageswap.

כדי לעשות את זה, דפדפנים יכולים עכשיו לחשוף אובייקטים של NavigationActivation שמכילים מידע על ניווט מאותו מקור. האובייקט הזה חושף את סוג הניווט שבו נעשה שימוש, את הרשומות של היסטוריית היעדים הנוכחיות והסופיות כפי שהן מופיעות ב-navigation.entries() מה-API של הניווט.

בדף מופעל, ניתן לגשת לאובייקט הזה דרך navigation.activation. באירוע pageswap, אפשר לגשת לכתובת הזו דרך e.activation.

כדאי לצפות בהדגמה הזו של פרופילים שמשתמשת בפרטי NavigationActivation באירועים pageswap ו-pagereveal כדי להגדיר את ערכי ה-view-transition-name באלמנטים שצריכים להשתתף במעבר התצוגה.

כך אין צורך לקשט כל פריט ברשימה עם view-transition-name מראש. במקום זאת, התהליך הזה מתרחש בדיוק בזמן באמצעות JavaScript, רק ברכיבים שזקוקים לו.

הקלטה של הדגמת הפרופילים. נדרש Chrome 126 ואילך.

זהו הקוד:

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

המתנה לטעינת תוכן עם חסימת רינדור

תמיכה בדפדפן

  • 124
  • 124
  • x
  • x

מקור

במקרים מסוימים, כדאי להשהות את העיבוד הראשון של דף עד שרכיב מסוים יופיע ב-DOM החדש. כך אפשר למנוע הבהוב ולוודא שהמצב שאליו אתם מפעילים את האנימציה יציב.

ב-<head>, מגדירים מזהה רכיב אחד או יותר שצריכים להיכלל לפני שהדף מקבל את העיבוד הראשון, באמצעות המטא תג הבא.

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

משמעות המטא תג הזה היא שהאלמנט צריך להיות קיים ב-DOM, ולא שצריך לטעון את התוכן. לדוגמה, בתמונות Google, רק הנוכחות של התג <img> עם הערך שצוין id בעץ ה-DOM מספיקה כדי שהתנאי יתקיים. ייתכן שהתמונה עצמה עדיין נטענת.

לפני שתתחילו להשתמש בחסימת עיבוד, חשוב לזכור שעיבוד מצטבר הוא היבט בסיסי של האינטרנט, לכן חשוב להיזהר כשבוחרים לחסום את העיבוד. צריך להעריך כל מקרה לגופו ואת ההשפעה של חסימת הרינדור. כברירת מחדל, לא מומלץ להשתמש ב-blocking=render, אלא אם יש לך אפשרות למדוד ולאמוד באופן פעיל את ההשפעה שלו על המשתמשים על ידי מדידת ההשפעה על מדדי הליבה לבדיקת חוויית המשתמש באתר.


הצגה של סוגי מעברים בין תצוגות של מסמכים שונים

מעברים בין תצוגות של מסמכים שונים תומכים גם בסוגי מעברים בין תצוגות כדי להתאים אישית את האנימציות ואת האלמנטים שיתועדו.

לדוגמה, בעת מעבר לדף הבא או לדף הקודם של עימוד, ייתכן שתרצה להשתמש באנימציות שונות בהתאם למעבר לדף גבוה יותר או לדף נמוך יותר מהרצף.

כדי להגדיר את הסוגים האלה מראש, צריך להוסיף את הסוגים ברמת @view-transition:

@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.

הדגמה (דמו)

בהדגמת העימוד הבאה, תוכן הדף מחליק קדימה או אחורה על סמך מספר הדף שאליו מנווטים.

הקלטת הדגמת העימוד (MPA). יש בה מעברים שונים, בהתאם לדף שאליו מועברים.

סוג המעבר שיש להשתמש בו נקבע באירועים 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 במקום זאת.