בעזרת ResizeObserver
אפשר לדעת כשהגודל של הרכיב משתנה.
לפני ResizeObserver
היה עליך לצרף אוזן לאירוע resize
של המסמך כדי לקבל הודעה על כל שינוי במידות של אזור התצוגה. במקרה כזה, תצטרכו להבין אילו רכיבים הושפעו מהשינוי הזה ולקרוא לתרחיש ספציפי להגיב בהתאם. אם הייתם צריכים לדעת מהם המאפיינים החדשים של רכיב אחרי שינוי הגודל שלו, הייתם צריכים להפעיל את getBoundingClientRect()
או את getComputedStyle()
. הפעולות האלה עלולות לגרום לטרחה בפריסת הרכיבים אם לא תבצעו קיבוץ של כל הקריאות וכל הכתיבות.
הבעיה הזו לא נגעה אפילו למקרים שבהם הגודל של רכיבים משתנה בלי שינוי הגודל של החלון הראשי. לדוגמה, הוספת צאצאים חדשים, הגדרת הסגנון של display
ברכיב מסוים כ-none
או פעולות דומות, יכולות לשנות את הגודל של אלמנט, את אחיותיו או את ישויות האב שלו.
לכן, ResizeObserver
הוא רכיב שימושי. היא מגיבה לשינויים בגודל של הרכיבים שנמדדו, בלי קשר למה שגרם לשינוי.
הוא גם מספק גישה לגודל החדש של האלמנטים שנצפו.
API
כל ממשקי ה-API עם הסיומת Observer
שציינו למעלה חולקים עיצוב API פשוט. ResizeObserver
הוא לא יוצא מן הכלל. יוצרים אובייקט ResizeObserver
ומעבירים קריאה חוזרת למבנה (constructor). ל-callback מועבר מערך של אובייקטים מסוג 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
. תיבת התוכן היא התיבה שבה אפשר להציב תוכן. היא התיבה עם הגבול, פחות המילוי.
חשוב לציין ש-ResizeObserver
מדווח על המאפיינים של contentRect
ועל המילוי, אבל הוא צופה רק ב-contentRect
.
אין להתבלבל בין contentRect
לבין תיבת הגבול של האלמנט. התיבה המקיפה, כפי שמדווחת על ידי getBoundingClientRect()
, היא התיבה שמכילה את הרכיב כולו ואת הצאצאים שלו. קובצי SVG הם יוצאי דופן מהכלל, ופונקציית ResizeObserver
תדווח על המימדים של תיבת הגבול.
החל מגרסה 84 של Chrome, ל-ResizeObserverEntry
יש שלושה מאפיינים חדשים שמספקים מידע מפורט יותר. כל אחד מהנכסים האלה מחזיר אובייקט ResizeObserverSize
שמכיל את המאפיין blockSize
ואת המאפיין inlineSize
. המידע הזה מתייחס לרכיב שנצפה בזמן ההפעלה של פונקציית הקריאה החוזרת.
borderBoxSize
contentBoxSize
devicePixelContentBoxSize
כל הפריטים האלה מחזירים מערכי קריאה בלבד, כי אנחנו מקווים שבעתיד הם יוכלו לתמוך ברכיבים עם כמה קטעים, שמתרחשים בתרחישים עם כמה עמודות. בשלב זה, המערכים האלה יכללו רק רכיב אחד.
התמיכה בפלטפורמות בנכסים האלה מוגבלת, אבל Firefox כבר תומך בשניים הראשונים.
מתי מתבצע הדיווח?
המפרט קובע ש-ResizeObserver
צריך לעבד את כל האירועים של שינוי גודל לפני הצבע ואחרי הפריסה. לכן, קריאה חוזרת של ResizeObserver
היא המקום האידיאלי לבצע שינויים בפריסת הדף. מאחר שעיבוד ResizeObserver
מתבצע בין הפריסה לבין הצביעה, הפעולה הזו תגרום לפסילה של הפריסה בלבד, ולא של הצביעה.
הבנתי
יכול להיות שתהיו סקרנים לדעת מה קורה אם משנים את הגודל של רכיב שנצפה בתוך פונקציית ה-callback ל-ResizeObserver
. התשובה היא: תגרמו להפעלה מיידית של שיחה חוזרת. למרבה המזל, ל-ResizeObserver
יש מנגנון שמאפשר למנוע לולאות קריאה חוזרת אינסופיות ותלות מעגלית. השינויים יעובדו באותה מסגרת רק אם הרכיב שגודלו השתנה נמצא עמוק יותר בעץ ה-DOM מאשר הרכיב shallowest שעבר עיבוד בקריאה החוזרת הקודמת.
אחרת, הן יועברו למסגרת הבאה.
אפליקציה
אחת מהאפשרויות שמאפשרת 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 אלפיות השנייה או פחות – אפשר לומר שהדף מגיב באופן מהימן לאינטראקציות של המשתמשים איתו.
משך הזמן שחולף עד להפעלת קריאות חזרה (callbacks) של אירועים בתגובה לאינטראקציה של משתמש יכול להשפיע באופן משמעותי על זמן האחזור הכולל של האינטראקציה, אבל זה לא ההיבט היחיד של INP שצריך להביא בחשבון. INP גם לוקח בחשבון את משך הזמן שנדרש עד הצבע הבא של האינטראקציה. זהו משך הזמן שחולף עד שהשלמת פעולת הרינדור הנדרשת כדי לעדכן את ממשק המשתמש בתגובה לאינטראקציה.
לגבי ResizeObserver
, הדבר חשוב כי קריאת החזרה (callback) שמופעלת על ידי מופע של ResizerObserver
מתרחשת לפני עבודת העיבוד. הסיבה לכך היא שצריך לקחת בחשבון את העבודה שמתבצעת בקריאה החוזרת, ולכן סביר מאוד להניח שכדי לבצע את הפעולה הזו יהיה צורך בשינוי בממשק המשתמש.
חשוב לבצע כמה שפחות עיבוד בקריאה חוזרת (callback) של ResizeObserver
, כי עיבוד יתר עלול לגרום למצבים שבהם הדפדפן יתעכב בביצוע משימות חשובות. לדוגמה, אם לאינטראקציה כלשהי יש קריאה חוזרת (callback) שגורמת להפעלה של קריאה חוזרת מסוג ResizeObserver
, חשוב לוודא שאתם מבצעים את הפעולות הבאות כדי לספק את חוויית השימוש הטובה ביותר:
- חשוב לוודא שהסלקטורים ב-CSS יהיו פשוטים ככל האפשר כדי להימנע מעבודה מיותרת של חישוב מחדש של סגנונות. חישובים מחדש של סגנונות מתבצעים ממש לפני הפריסה, וסלקטורים מורכבים של CSS יכולים לעכב פעולות פריסה.
- מומלץ להימנע מביצוע פעולות בקריאה החוזרת (callback) של
ResizeObserver
שעלולות לגרום לזרימות אוטומטיות חוזרות. - בדרך כלל, הזמן הנדרש לעדכון הפריסה של דף עולה ככל שמספר רכיבי ה-DOM בדף גדל. זה נכון גם אם הדפים משתמשים ב-
ResizeObserver
וגם אם לא, אבל ככל שהמורכבות המבנית של הדף גדלה, כך העבודה שמתבצעת בקריאה החוזרת (callback) שלResizeObserver
עשויה להיות משמעותית יותר.
סיכום
ResizeObserver
זמין בכל הדפדפנים העיקריים, והוא מספק דרך יעילה למעקב אחרי שינוי הגודל של רכיבים ברמת הרכיב. רק חשוב להיזהר לא לעכב את העיבוד יותר מדי באמצעות ממשק ה-API החזק הזה.