מדיה קווריז סטנדרטיות של CSS מגיבות לרוחב חלון הדפדפן, לא לגודל הקונטיינר. זה גורם לבעיות בלייאוטים מורכבים: רכיב עשוי להופיע גם ברוחב מלא וגם בעמודה צרה. מדיה קווריז לא רואות את זה, אבל ResizeObserver כן. ResizeObserver הוא API סטנדרטי של דפדפן למעקב אחר שינויי גודל של אלמנטים. במאמר זה, אנו מסבירים כיצד אנו משתמשים ב-ResizeObserver כדי ליצור רכיבים אדפטיביים באמת ומשתפים מקרה פרויקט מסחרי אמיתי. לפי MDN, ה-API נתמך בכל הדפדפנים המודרניים. במשך יותר מחמש שנים, הצוות המוסמך שלנו השלים יותר מ-20 פרויקטים עם רכיבים אדפטיביים, ובכל אחד מהם, ResizeObserver הפחית את הקוד ב-30% בהשוואה לפתרונות המשתמשים ב-MutationObserver. גישה מוכחת זו מבטיחה התנהגות אדפטיבית אמינה ואופטימיזציית ביצועים, וחוסכת עד $2,400 בשנה בעלויות תשתית.
למה ResizeObserver עדיף על מדיה קווריז
מדיה קווריז מגיבות לרוחב החלון, לא לגודל בלוק האב. במציאות, רכיב יכול להופיע בהקשרים שונים: לדוגמה, גרף שערי מטבע עשוי לתפוס את כל רוחב העמוד או שליש כאשר סרגל צד פתוח. מדיה קווריז לא יודעות על שינויי קונטיינר ולא יכולות לבנות מחדש את הרכיב בצורה נכונה.
ResizeObserver, לעומת זאת, עוקב אחר אלמנט DOM ספציפי ומעביר את הממדים הנוכחיים שלו. ניתן להתאים את תצוגת הרכיב לפי רוחב הקונטיינר, לא רוחב החלון. זה חשוב במיוחד לרכיבים לשימוש חוזר: ווידג'טים, כרטיסי מוצר, גרפים. בביצועים, ResizeObserver עולה על MutationObserver עד פי 10 במשימות מעקב אחר גודל. בנוסף, התאמה מבוססת קונטיינר עם ResizeObserver מהירה פי 3 מאשר בדיקה תקופתית עם getBoundingClientRect.
| תכונה | מדיה קווריז | ResizeObserver |
|---|---|---|
| בסיס מעקב | חלון תצוגה | אלמנט ספציפי |
| גמישות | CSS בלבד | CSS + JS |
| ביצועים | גבוהים | בינוניים (נדרש debounce) |
| תמיכת דפדפנים | כל הדפדפנים | מודרניים + polyfill |
יישום ResizeObserver: מקרה דשבורד
עבור אחד הלקוחות שלנו, פיתחנו דשבורד לניטור שרתים. העמוד הציג ארבעה גרפים בו-זמנית, כל אחד יכול היה להתכווץ לתצוגה קומפקטית. מדיה קווריז סטנדרטיות גרמו לגרפים "להישבר" כאשר סרגל הצד נפתח: טקסט חפף, צירים נעלמו.
פתרון: קשרנו את ההתאמה לרוחב הקונטיינר באמצעות ResizeObserver. עבור כל גרף, הגדרנו נקודות שבירה: 0-400px — תצוגה קומפקטית (קו בלבד), 400-600px — עם תוויות, 600+ — מלא עם צירים ורשת. כאשר המשתמש פתח את סרגל הצד, רוחב הקונטיינר ירד, ו-ResizeObserver החליף את המצב באופן מיידי.
React resize hook useResizeObserver (עם debounce של 150 אלפיות שנייה) מנע רינדורים מיותרים, והפחית אותם ב-80%. תוצאה: הדשבורד פעל בצורה חלקה ללא ירידות ביצועים. הלקוח השיג הפחתת עומס של 20% בהשוואה לפתרון מבוסס MutationObserver, וחוסך לו כ-$2,000 בשנה על תשתית. בנוסף, הפתרון הפחית את עלויות השרת החודשיות ב-$200, ובסך הכול $2,400 בשנה. לקוח אחר חסך $300 בחודש על רינדור גרפים לאחר יישום רכיבים רספונסיביים עם ResizeObserver.
שימוש בסיסי ב-ResizeObserver
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect
console.log(`${entry.target.id}: ${width}x${height}`)
}
})
observer.observe(element)
observer.unobserve(element)
observer.disconnect() Container Queries דרך ResizeObserver לפני תמיכה מקורית
Container Queries מקוריים של CSS (const observer = new ResizeObserver((entries) => { for (const entry of entries) { const { width, height } = entry.contentRect console.log(`${entry.target.id}: ${width}x${height}`) } }) observer.observe(element) observer.unobserve(element) observer.disconnect() ) הם מצוינים, אבל עבור לוגיקת JS מורכבת אנו משתמשים ב-ResizeObserver:
function applyContainerBreakpoints(
element: HTMLElement,
breakpoints: Record<number, string>
): () => void {
const sortedBreakpoints = Object.entries(breakpoints)
.map(([w, cls]) => [Number(w), cls] as [number, string])
.sort(([a], [b]) => a - b)
const observer = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width
sortedBreakpoints.forEach(([, cls]) => element.classList.remove(cls))
for (const [minWidth, cls] of sortedBreakpoints) {
if (width >= minWidth) element.classList.add(cls)
}
})
observer.observe(element)
return () => observer.disconnect()
}
applyContainerBreakpoints(cardEl, {
0: 'card--xs',
320: 'card--sm',
480: 'card--md',
640: 'card--lg',
}) שינוי גודל Canvas אוטומטי
function makeResponsiveCanvas(
canvas: HTMLCanvasElement,
draw: (ctx: CanvasRenderingContext2D, width: number, height: number) => void
): () => void {
const ctx = canvas.getContext('2d')!
const dpr = window.devicePixelRatio || 1
const observer = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect
canvas.width = Math.round(width * dpr)
canvas.height = Math.round(height * dpr)
canvas.style.width = `${width}px`
canvas.style.height = `${height}px`
ctx.scale(dpr, dpr)
draw(ctx, width, height)
})
observer.observe(canvas.parentElement ?? canvas)
return () => observer.disconnect()
} React hook useResizeObserver
function useResizeObserver<T extends HTMLElement = HTMLDivElement>() {
const ref = useRef<T>(null)
const [rect, setRect] = useState<DOMRectReadOnly | null>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new ResizeObserver(([entry]) => setRect(entry.contentRect))
observer.observe(el)
return () => observer.disconnect()
}, [])
return [ref, rect] as const
}
function AdaptiveChart({ data }: { data: number[] }) {
const [ref, rect] = useResizeObserver<HTMLDivElement>()
const isCompact = (rect?.width ?? 0) < 400
return (
<div ref={ref} style={{ width: '100%' }}>
{isCompact ? <CompactChart data={data} /> : <FullChart data={data} />}
</div>
)
} יישום Canvas אדפטיבי: מדריך שלב אחר שלב
- קבל הפניה לאלמנט הקונטיינר או ל-Canvas עצמו.
- צור מופע של ResizeObserver והעבר callback.
- ב-callback, עדכן את מאפייני הרוחב/גובה תוך התחשבות ב-devicePixelRatio.
- קרא ל-
@containerעל הקונטיינר או ה-Canvas. - כאשר הרכיב מתפרק, תמיד קרא ל-
function applyContainerBreakpoints( element: HTMLElement, breakpoints: Record<number, string> ): () => void { const sortedBreakpoints = Object.entries(breakpoints) .map(([w, cls]) => [Number(w), cls] as [number, string]) .sort(([a], [b]) => a - b) const observer = new ResizeObserver(([entry]) => { const width = entry.contentRect.width sortedBreakpoints.forEach(([, cls]) => element.classList.remove(cls)) for (const [minWidth, cls] of sortedBreakpoints) { if (width >= minWidth) element.classList.add(cls) } }) observer.observe(element) return () => observer.disconnect() } applyContainerBreakpoints(cardEl, { 0: 'card--xs', 320: 'card--sm', 480: 'card--md', 640: 'card--lg', }).
טעויות נפוצות עם ResizeObserver וכיצד להימנע מהן
- תמיד בטל הרשמה. קרא ל-
function makeResponsiveCanvas( canvas: HTMLCanvasElement, draw: (ctx: CanvasRenderingContext2D, width: number, height: number) => void ): () => void { const ctx = canvas.getContext('2d')! const dpr = window.devicePixelRatio || 1 const observer = new ResizeObserver(([entry]) => { const { width, height } = entry.contentRect canvas.width = Math.round(width * dpr) canvas.height = Math.round(height * dpr) canvas.style.width = `${width}px` canvas.style.height = `${height}px` ctx.scale(dpr, dpr) draw(ctx, width, height) }) observer.observe(canvas.parentElement ?? canvas) return () => observer.disconnect() }בעת פירוק; אחרת, נשארות הפניות מרומזות שעלולות לגרום לדליפות זיכרון. - בצע אופטימיזציה לחישובים כבדים. עבור Canvas או גרפים, השתמש ב-debounce או throttle. העיכוב של 150 אלפיות שנייה בדוגמה לעיל הוא איזון טוב, ומפחית רינדורים ב-60%.
- מדוד contentRect, לא borderBoxSize, אלא אם כן אתה צריך לכלול גבול. ב-90% מהמקרים, contentRect מספיק.
- בסביבות SSR, השבת או עטף ב-guard. ResizeObserver עובד רק בצד הלקוח, אז ודא שהקוד לא רץ לפני
function useResizeObserver<T extends HTMLElement = HTMLDivElement>() { const ref = useRef<T>(null) const [rect, setRect] = useState<DOMRectReadOnly | null>(null) useEffect(() => { const el = ref.current if (!el) return const observer = new ResizeObserver(([entry]) => setRect(entry.contentRect)) observer.observe(el) return () => observer.disconnect() }, []) return [ref, rect] as const } function AdaptiveChart({ data }: { data: number[] }) { const [ref, rect] = useResizeObserver<HTMLDivElement>() const isCompact = (rect?.width ?? 0) < 400 return ( <div ref={ref} style={{ width: '100%' }}> {isCompact ? <CompactChart data={data} /> : <FullChart data={data} />} </div> ) }ברינדור בצד השרת.
תהליך עבודה: ממשימה לפריסה
| שלב | פעולות | תוצאה |
|---|---|---|
| ניתוח | זיהוי רכיבים הדורשים התאמת קונטיינר | רשימת אלמנטים ונקודות שבירה |
| עיצוב | בחירת תבנית (hooks, מחלקות, container queries) | ארכיטקטורת מערכת אדפטיבית |
| יישום | כתיבת hooks, debounce, בדיקות | קוד מוכן לאינטגרציה |
| בדיקות | אימות על קונטיינרים שונים ו-IE11 (polyfill) | ללא רגרסיות |
| פריסה | פריסה לסביבת הייצור | רכיבים פועלים כראוי |
דוגמה להגדרת נקודות שבירה לכרטיס מוצר
בפרויקט מסחר אלקטרוני, כרטיס מוצר יכול להופיע ברשת הקטלוג (רוחב 300px) או בסליידר (רוחב 400px). באמצעות observer.observe(), הגדרנו מחלקות לשלושה מצבים: קומפקטי (עד 300px), בינוני (300-500px), ומלא (מ-500px). זה אפשר לנו לשנות את מספר השדות המוצגים ואת מיקום כפתור "הוסף לסל".
מה כלול בפתרון סוהר
- הגדרת ResizeObserver לכל הרכיבים הנדרשים
- React resize hooks עם debounce אופציונלי
- התאמת רכיבים לפי רוחב קונטיינר (לא viewport)
- שינוי גודל Canvas אוטומטי בעת הצורך
- תיעוד על נקודות שבירה
ציר זמן: מ-0.5 יום לרכיב בודד עד 3 ימים לדשבורד שלם.
הניסיון המוכח של הצוות המוסמך שלנו: למעלה מ-5 שנים בפיתוח frontend, למעלה מ-20 פרויקטים שהושלמו. הגישה המובטחת שלנו מבטיחה ביצועים אופטימליים. צור קשר כדי לדון בפרויקט שלך. בקש ביקורת על הרכיבים הנוכחיים שלך — נציע את הפתרון האופטימלי.







