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

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

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

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

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

תמיכה בדפדפן

  • 64
  • 79
  • 69
  • 13.1

מקור

API

לכל ממשקי ה-API עם הסיומת Observer שהזכרנו למעלה יש עיצוב פשוט של API. המדיניות ResizeObserver אינה יוצאת מן הכלל. יוצרים אובייקט ResizeObserver ומעבירים קריאה חוזרת (callback) ל-constructor. הקריאה החוזרת מועברת מערך של 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 מדווח גם על המאפיינים של contentRect וגם של המרווח הפנימי, הוא רק צופה ב-contentRect. אין לבלבל בין contentRect לבין התיבה התוחמת של הרכיב. התיבה התוחמת, כפי שמדווח על ידי getBoundingClientRect(), היא התיבה שמכילה את הרכיב כולו ואת הצאצאים שלו. קובצי SVG הם יוצאי דופן לכלל, שבהם ResizeObserver ידווח על המידות של התיבה התוחמת.

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

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

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

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

מתי הוא מדווח?

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

הבנתי

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

בקשת הצטרפות

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

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)

Interaction to Next Paint (INP) הוא מדד שמודד את הרספונסיביות הכוללת של הדף לאינטראקציות של משתמשים. במקרה שה-INP של דף נמצא בסף 'הטוב', כלומר 200 אלפיות השנייה או פחות, הדף מגיב בצורה אמינה לאינטראקציות של המשתמש איתו.

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

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

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

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

סיכום

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