sizeObserver: דומה ל-document.onresize לרכיבים

ResizeObserver מודיע לכם כשגודל של רכיב משתנה.

לפני ResizeObserver, הייתם צריכים לצרף listener לאירוע resize של המסמך כדי לקבל התראה על כל שינוי בממדי אזור התצוגה. ב-event handler, תצטרכו להבין אילו רכיבים הושפעו מהשינוי הזה ולקרוא לשגרה ספציפית כדי להגיב בצורה מתאימה. אם הייתם צריכים את המידות החדשות של רכיב אחרי שינוי הגודל, הייתם צריכים להפעיל את getBoundingClientRect() או את getComputedStyle(), מה שיכול לגרום לבעיות בפריסה אם לא תדאגו לאגד את כל הקריאות וכל הכתיבות.

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

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

Browser Support

  • Chrome: 64.
  • Edge: 79.
  • Firefox: 69.
  • Safari: 13.1.

Source

API

לכל ממשקי ה-API עם הסיומת Observer שציינו למעלה יש עיצוב פשוט. ResizeObserver לא שונה מהשאר. יוצרים אובייקט ResizeObserver ומעבירים קריאה חוזרת לקונסטרוקטור. פונקציית הקריאה החוזרת מקבלת מערך של אובייקטים מסוג ResizeObserverEntry – רשומה אחת לכל רכיב שנצפה – שמכיל את המידות החדשות של הרכיב.

var ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;

    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

פרטים מסוימים

מה מדווח?

באופן כללי, ResizeObserverEntry מדווח על תיבת התוכן של רכיב באמצעות מאפיין שנקרא contentRect, שמחזיר אובייקט DOMRectReadOnly. תיבת התוכן היא התיבה שבה אפשר להציב תוכן. הוא התיבה התוחמת פחות הריווח הפנימי.

תרשים של מודל התיבה של CSS.

חשוב לזכור שResizeObserver reports גם את המאפיינים של contentRect וגם את הריווח הפנימי, אבל הוא רק watches את contentRect. אל תתבלבלו בין contentRect לבין תיבת התוחמת של הרכיב. תיבת התוחמת, כפי שמדווחת על ידי getBoundingClientRect(), היא התיבה שמכילה את כל הרכיב ואת צאצאיו. קובצי SVG הם יוצאים מן הכלל, ובמקרה שלהם הפונקציה ResizeObserver תחזיר את המידות של התיבה התוחמת.

החל מ-Chrome 84, ל-ResizeObserverEntry יש שלושה מאפיינים חדשים שמספקים מידע מפורט יותר. כל אחד מהמאפיינים האלה מחזיר אובייקט ResizeObserverSize שמכיל את המאפיין blockSize ואת המאפיין inlineSize. המידע הזה מתייחס לרכיב שנצפה בזמן הפעלת הקריאה החוזרת.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

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

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

מתי מתבצע הדיווח?

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

הבנתי

יכול להיות שאתם שואלים את עצמכם: מה קורה אם משנים את הגודל של רכיב שנצפה בתוך פונקציית הקריאה החוזרת ל-ResizeObserver? התשובה היא: תופעל קריאה חוזרת (callback) נוספת מיד. למזלנו, ל-ResizeObserver יש מנגנון למניעת לולאות אינסופיות של קריאות חוזרות ותלויות מחזוריות. השינויים יעובדו רק באותו פריים אם הרכיב שגודלו שונה נמצא עמוק יותר בעץ ה-DOM מהרכיב הכי שטחי שעובד בקריאה החוזרת הקודמת. אחרת, הם יועברו למסגרת הבאה.

אפליקציה

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

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius =
        Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));

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

ResizeObserver מאפשר לכם לכתוב חלק אחד של קוד שמטפל בשני התרחישים. שינוי הגודל של החלון הוא אירוע שאפשר לתעד באמצעות ResizeObserver, אבל קריאה ל-appendChild() משנה גם את הגודל של הרכיב הזה (אלא אם מוגדר overflow: hidden), כי צריך לפנות מקום לרכיבים החדשים. לכן, כדי להשיג את האפקט הרצוי, צריך לכתוב מעט מאוד שורות:

const ro = new ResizeObserver(entries => {
  document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
});

// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);

// Observe the timeline to process new messages
ro.observe(timeline);

מגניב, נכון?

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

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

השפעות על מהירות התגובה לאינטראקציה באתר (INP)

מהירות התגובה לאינטראקציה באתר (INP) הוא מדד שמודד את רמת הרספונסיביות הכוללת של דף לאינטראקציות של משתמשים. אם ערך ה-INP של דף מסוים נמצא בסף 'טוב' – כלומר, 200 אלפיות השנייה או פחות – אפשר לומר שהדף מגיב באופן מהימן לאינטראקציות של המשתמשים איתו.

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

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

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

  • כדי למנוע חישוב מחדש מוגזם של סגנונות, חשוב לוודא שהסלקטורים ב-CSS פשוטים ככל האפשר. חישובים מחדש של סגנונות מתבצעים ממש לפני הפריסה, וסלקטורים מורכבים של CSS יכולים לעכב פעולות פריסה.
  • אל תבצעו עבודה בפונקציית הקריאה החוזרת (callback) של ResizeObserver שעלולה להפעיל הצגה מחדש מאולצת.
  • הזמן שנדרש לעדכון הפריסה של דף בדרך כלל גדל עם מספר רכיבי ה-DOM בדף. הדבר נכון גם אם הדפים משתמשים ב-ResizeObserver וגם אם לא, אבל ככל שהמורכבות המבנית של הדף גדלה, העבודה שמתבצעת בקריאה חוזרת (callback) של ResizeObserver יכולה להיות משמעותית.

סיכום

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