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

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

Browser Support

  • Chrome: 126.
  • Edge: 126.
  • Firefox: not supported.
  • Safari: 18.2.

Source

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

  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, כי אלה מקורות שונים באותו אתר.


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

כדי להגדיר מעבר בין שני מסמכים בתצוגה של כמה מסמכים, צריך להביע הסכמה בשני הדפים שמשתתפים במעבר. הפעולה הזו מתבצעת באמצעות כלל ה-at ‏@view-transition ב-CSS.

בכלל @view-transition, מגדירים את המתאר navigation ל-auto כדי להפעיל מעברים בין תצוגות לניווטים ממקורות שונים באותו מסמך.

@view-transition {
  navigation: auto;
}

אם מגדירים את navigation descriptor לערך auto, מאשרים את השימוש במעברים בין תצוגות עבור סוגי NavigationType הבאים:

  • traverse
  • push או replace, אם ההפעלה לא בוצעה על ידי המשתמש באמצעות מנגנונים של ממשק המשתמש בדפדפן.

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

אם הניווט נמשך יותר מדי זמן – יותר מארבע שניות במקרה של Chrome – אז המעבר בין התצוגות מדלג עם TimeoutError DOMException.

.

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

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

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

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

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

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

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

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: 18.2.

Source

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

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: 147.
  • Safari: 26.2.

Source

גם באירועים 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, רק ברכיבים שזקוקים לכך.

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

הקוד הוא:

// 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-names.

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

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

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: not supported.

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

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

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

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

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