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