איך להשתמש עכשיו בשאילתות קונטיינר

לאחרונה, כריס קוייר כתב פוסט בבלוג שהעלה את השאלה הבאה:

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

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

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

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

גישה פרגמטית

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

לאחר מכן נשאלת השאלה: עד כמה החלופה צריכה להיות מקיפה?

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

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

כמעט כל מערכות העיצוב המודרניות וספריות הרכיבים סטנדרטיות לפי עקרונות מותאמים לניידים, מיושמות באמצעות קבוצה של נקודות עצירה (breakpoint) מוגדרות מראש (כמו SM, MD, LG, XL). הרכיבים עוברים אופטימיזציה להצגה טובה במסכים קטנים כברירת מחדל, ולאחר מכן סגנונות משולבים בשכבות כדי לתמוך בקבוצה קבועה של רוחבי מסך גדולים יותר. (דוגמאות לכך אפשר למצוא במסמכי התיעוד של Bootstrap ושל Tailwind).

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

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

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

איך זה עובד

שלב 1: עדכון סגנונות הרכיבים לשימוש בכללי @container במקום בכללי @media

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

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

אחרי שמזהים את הרכיבים שרוצים לעדכן, צריך לשנות כל אחד מהכללים של @media ברכיבים האלה CSS לכלל @container.

לפניכם דוגמה לאופן שבו זה יכול להיראות על רכיב .photo-gallery שכברירת מחדל הוא עמודה אחת, ולאחר מכן משתמש בכללי @media כדי לעדכן את הפריסה שלו כך שתהפוך לשתי עמודות ושלוש עמודות בנקודות העצירה MD ו-X (בהתאמה):

.photo-gallery {
  display: grid;
  grid-template-columns: 1fr;
}

/* Styles for the `MD` breakpoint */
@media (min-width: 800px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Styles for the `XL` breakpoint */
@media (min-width: 1200px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

כדי לעדכן את הרכיב .photo-gallery כך שישתמש בכללי @container, קודם צריך להחליף את המחרוזת @media במחרוזת @container ב-CSS. הדקדוק של שני הכללים האלה דומה מספיק כך שבמקרים רבים זה כל מה שצריך לשנות.

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

לדוגמה, אם הסגנונות של ה-CSS .photo-gallery בנקודות העצירה (breakpoint) MD ו-XL בדוגמה הקודמת נניח שסרגל הצד בגודל 200 פיקסלים יוצג בנקודות העצירה האלה, תנאי הגודל של כללי @container צריכים להיות פחות מ-200 פיקסלים – בהנחה ש"הקונטיינר". של הרכיב .photo-gallery לא יכלול את סרגל הצד.

בסך הכול, כדי להמיר את שירות ה-CSS .photo-gallery מכללי @media לכללי @container, קבוצת השינויים המלאה היא:

/* Before, using the original breakpoint sizes: */
@media (min-width: 800px) { /* ... */ }
@media (min-width: 1200px) { /* ... */ }

/* After, with the breakpoint sizes reduced by 200px: */
@container (min-width: 600px) { /* ... */ }
@container (min-width: 1000px) { /* ... */ }

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

אחרי שמעדכנים את סגנונות הרכיבים מכללי @media לכללי @container, השלב הבא הוא להגדיר את רכיבי המאגר.

שלב 2: מוסיפים ל-HTML רכיבי קונטיינר

בשלב הקודם הוגדר סגנונות רכיבים שמבוססים על הגודל של רכיב קונטיינר. בשלב הבא מגדירים אילו רכיבים בדף צריכים להיות רכיבי המאגר שבהם כללי @container יהיו יחסיים.

כדי להצהיר שכל רכיב הוא רכיב קונטיינר ב-CSS, אפשר להגדיר את המאפיין container-type שלו ל-size או ל-inline-size. אם כללי המאגר הם מבוססי-רוחב, בדרך כלל כדאי להשתמש ב-inline-size.

נניח שיש אתר עם מבנה HTML בסיסי הבא:

<body>
  <div class="sidebar">...</div>
  <div class="content">...</div>
</body>

כדי ליצור את הרכיבים .sidebar ו-.content באתר הזה כקונטיינרים, צריך להוסיף את הכלל הבא לשירות ה-CSS:

.content, .sidebar {
  container-type: inline-size;
}

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

עם זאת, בדפדפנים שלא תומכים בשאילתות של מאגרי תגים יש צורך בפעולות נוספות.

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

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

הקוד הבא מגדיר רכיב <responsive-container> לשימוש חוזר, שמאזינים באופן אוטומטי לשינויי גודל ומוסיף סיווגים של נקודות עצירה (breakpoint) ששירות ה-CSS יכול לעצב לפיהם:

// A mapping of default breakpoint class names and min-width sizes.
// Redefine these (or add more) as needed based on your site's design.
const defaultBreakpoints = {SM: 400, MD: 600 LG: 800, XL: 1000};

// A resize observer that monitors size changes to all <responsive-container>
// elements and calls their `updateBreakpoints()` method with the updated size.
const ro = new ResizeObserver((entries) => {
  entries.forEach((e) => e.target.updateBreakpoints(e.contentRect));
});

class ResponsiveContainer extends HTMLElement {
  connectedCallback() {
    const bps = this.getAttribute('breakpoints');
    this.breakpoints = bps ? JSON.parse(bps) : defaultBreakpoints;
    this.name = this.getAttribute('name') || '';
    ro.observe(this);
  }
  disconnectedCallback() {
    ro.unobserve(this);
  }
  updateBreakpoints(contentRect) {
    for (const bp of Object.keys(this.breakpoints)) {
      const minWidth = this.breakpoints[bp];
      const className = this.name ? `${this.name}-${bp}` : bp;
      this.classList.toggle(className, contentRect.width >= minWidth);
    }
  }
}

self.customElements.define('responsive-container', ResponsiveContainer);

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

לדוגמה, אם הערך של width של הרכיב <responsive-container> הוא בין 600 ל-800 פיקסלים (על סמך ערכי ברירת המחדל של נקודות העצירה (breakpoint) המוגדרים בקוד), יתווספו המחלקות SM ו-MD, באופן הבא:

<responsive-container class="SM MD">...</responsive-container>

המחלקות האלה מאפשרות להגדיר סגנונות חלופיים לדפדפנים שלא תומכים בשאילתות של מאגר תגים (מידע נוסף זמין בקטע שלב 3: הוספת סגנונות חלופיים ל-CSS).

כדי לעדכן את קוד ה-HTML הקודם כך שישתמש ברכיב הקונטיינר הזה, צריך לשנות את סרגל הצד ואת רכיבי <div> התוכן הראשי כך שיהיו רכיבי <responsive-container>:

<body>
  <responsive-container class="sidebar">...</responsive-container>
  <responsive-container class="content">...</responsive-container>
</body>

ברוב המקרים אפשר פשוט להשתמש ברכיב <responsive-container> ללא התאמה אישית, אבל אם בכל זאת צריך להתאים אותו, האפשרויות הבאות זמינות:

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

לפניכם דוגמה שמגדירה את שתי אפשרויות ההגדרה האלה:

<responsive-container
  name='sidebar'
  breakpoints='{"bp4":400,"bp5":500,"bp6":600,"bp7":700,"bp8":800,"bp9":900,"bp10":1000}'>
</responsive-container>

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

if (!CSS.supports('container-type: inline-size')) {
  import('./path/to/responsive-container.js');
}

שלב 3: הוספת סגנונות חלופיים אל ה-CSS

השלב האחרון בשיטה הזו הוא להוסיף סגנונות חלופיים לדפדפנים שלא מזהים את הסגנונות שהוגדרו בכללים של @container. כדי לעשות זאת, אפשר לשכפל את הכללים האלה באמצעות המחלקות של נקודות העצירה שהוגדרו ברכיבי <responsive-container>.

בהמשך לדוגמה של .photo-gallery הקודמת, הסגנונות החלופיים עבור שני כללי @container עשויים להיראות כך:

/* Container query styles for the `MD` breakpoint. */
@container (min-width: 600px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Fallback styles for the `MD` breakpoint. */
@supports not (container-type: inline-size) {
  :where(responsive-container.MD) .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Container query styles for the `XL` breakpoint. */
@container (min-width: 1000px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

/* Fallback styles for the `XL` breakpoint. */
@supports not (container-type: inline-size) {
  :where(responsive-container.XL) .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

בקוד הזה, לכל כלל @container יש כלל מקביל שתואם לרכיב <responsive-container>, אם קיימת המחלקה המתאימה של נקודת העצירה (breakpoint).

החלק של הבורר שתואם לרכיב <responsive-container> מוקף בבורר פסאודו-מחלקה פונקציונלית של :where(), כדי לשמור על מידת הספציפיות של הבורר החלופי זהה למידת הספציפיות של הבורר המקורי בכלל @container.

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

החיסרון העיקרי של שיטת הגיבוי הזו הוא צורך לחזור על הצהרת סגנון פעמיים – זה מסובך ומעורר יותר שגיאות. עם זאת, אם אתם משתמשים במעבד מראש של CSS, אתם יכולים להפשט אותו לתמהיל שיוצר עבורכם גם את הכלל @container וגם את קוד הגיבוי. הנה דוגמה באמצעות Sass:

@use 'sass:map';

$breakpoints: (
  'SM': 400px,
  'MD': 600px,
  'LG': 800px,
  'XL': 1000px,
);

@mixin breakpoint($breakpoint) {
  @container (min-width: #{map.get($breakpoints, $breakpoint)}) {
    @content();
  }
  @supports not (container-type: inline-size) {
    :where(responsive-container.#{$breakpoint}) & {
      @content();
    }
  }
}

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

.photo-gallery {
  display: grid;
  grid-template-columns: 1fr;

  @include breakpoint('MD') {
    grid-template-columns: 1fr 1fr;
  }

  @include breakpoint('XL') {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

זה הכול!

Recap

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

  1. רכיבי הזהות שרוצים לעצב ביחס למאגר התגים שלהם, ולעדכן את הכללים של @media ב-CSS כך שישתמשו בכללי @container. כמו כן, (אם עדיין לא עשיתם זאת), כדאי ליצור סטנדרטיזציה לקבוצת שמות של נקודות עצירה כדי להתאים לתנאי הגודל בכללי המאגר שלכם.
  2. צריך להוסיף את ה-JavaScript שמפעיל את הרכיב <responsive-container> המותאם אישית, ולאחר מכן להוסיף את הרכיב <responsive-container> לכל אזור תוכן בדף שבו רוצים שהרכיבים יהיו יחסיים.
  3. כדי לתמוך בדפדפנים ישנים יותר, צריך להוסיף ל-CSS סגנונות חלופיים שתואמים למחלקות של נקודות עצירה (breakpoint), שנוספים באופן אוטומטי לרכיבי <responsive-container> ב-HTML. באופן אידיאלי, כדאי להשתמש בשילוב של מעבד מידע מקדים ב-CSS כדי להימנע מהצורך לכתוב את אותם סגנונות פעמיים.

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

רוצה לראות את זה בפעולה?

הדרך הטובה ביותר להבין איך כל השלבים האלה משתלבים ביחד היא לראות הדגמה של כל השלבים האלה.

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

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

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

אתם יכולים לבדוק את קוד המקור המלא של ההדגמה ב-GitHub, ולבדוק ספציפית את ה-CSS של רכיבי ההדגמה כדי לראות איך מוגדרים הסגנונות החלופיים. אם רוצים לבדוק רק את ההתנהגות החלופית, אפשר להשתמש בהדגמה של חלופה בלבד שכוללת את הווריאציה הזו בלבד – גם בדפדפנים שתומכים בשאילתות מסוג קונטיינר.

מגבלות ושיפורים פוטנציאליים

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

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

יחידות שאילתות של קונטיינרים

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

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

responsive-container {
  --cqw: 1cqw;
  --cqh: 1cqh;
}

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

.photo-gallery {
  font-size: calc(10 * var(--cqw));
}

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

class ResponsiveContainer extends HTMLElement {
  // ...
  updateBreakpoints(contentRect) {
    this.style.setProperty('--cqw', `${contentRect.width / 100}px`);
    this.style.setProperty('--cqh', `${contentRect.height / 100}px`);

    // ...
  }
}

כך תוכלו "לעבור" ביעילות את הערכים האלה מ-JavaScript ועד CSS, ואז יש לכם את מלוא העוצמה של CSS (לדוגמה, calc(), min(), max(), clamp()) לבצע בהם שינויים לפי הצורך.

תמיכה בתכונות לוגיות ובמצב כתיבה

יכול להיות שהבחנת בשימוש ב-inline-size במקום ב-width בהצהרות @container בחלק מהדוגמאות האלה ל-CSS. יכול להיות שהבחנת גם ביחידות החדשות cqi ו-cqb (לגדלים מוטבעים ובלוקים, בהתאמה). התכונות החדשות האלה משקפות את המעבר של שירות CSS למאפיינים ולערכים לוגיים במקום להשתמש בתכונות פיזיות או בכיוון מסוים.

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

אומנם ניתן לקבל את מצב הכתיבה באמצעות משהו כמו getComputedStyle() שמעביר את רכיב הקונטיינר, אבל אם עושים זאת יש עלות, ואין דרך ממש טובה לזהות אם מצב הכתיבה משתנה.

לכן, הגישה הטובה ביותר היא שהרכיב <responsive-container> עצמו יקבל נכס של מצב כתיבה שבעל האתר יכול להגדיר (ולעדכן) לפי הצורך. כדי ליישם את האפשרות הזו, צריך לפעול לפי אותה הגישה שצוינה בקטע הקודם, ולהחליף את הערכים width ו-height לפי הצורך.

מכלים מקוננים

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

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

לדוגמה, כאן יש שני רכיבי <responsive-container> שעוטפים את הרכיב .photo-gallery, אבל בגלל שהמאגר החיצוני גדול יותר מהמאגר הפנימי, נוספו להם מחלקות שונות של נקודות עצירה (breakpoint).

<responsive-container class="SM MD LG">
  ...
  <responsive-container class="SM">
    ...
    <div class="photo-gallery">...</div class="photo-gallery">
  </responsive-container>
</responsive-container>

בדוגמה הזו, המחלקה MD ו-LG בקונטיינר החיצוני תשפיע על כללי הסגנון שתואמים לרכיב .photo-gallery, ולא תואמים להתנהגות של שאילתות בקונטיינר (כי הן תואמות רק למאגר האב הקרוב ביותר).

כדי לטפל בבעיה, אפשר:

  1. חשוב תמיד לתת שם לכל הקונטיינרים שאתם מקוננים. כדי למנוע התנגשויות, חשוב לוודא שהמחלקות של נקודות העצירה (breakpoint) מופיעות בתחילת השם הזה של המאגר.
  2. כדאי להשתמש בשילוב הצאצא במקום בשילוב של הצאצאים בסלקטורים החלופיים (וזה קצת יותר מגביל).

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

מה לגבי דפדפנים שלא תומכים ב-:where(), ברכיבים מותאמים אישית או שינוי גודל של Observer?

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

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

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

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

למה לא להשתמש ב-polyfill של שאילתת קונטיינר?

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

מהסיבות האלה, בדרך כלל לא מומלץ להשתמש ב-polyfills של CSS בסביבת הייצור, כולל container-query-polyfill מ-Google Chrome Labs, שלא מתוחזק יותר (ויועד בעיקר למטרות הדגמה).

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

האם צריך גם להטמיע חלופה לדפדפנים ישנים יותר?

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

לפי caniuse.com, 90% ממשתמשי האינטרנט ברחבי העולם תומכים בשאילתות קונטיינרים, והרבה אנשים שקוראים את הפוסט הזה חושבים שיש סבירות גבוהה שהמספר גבוה יותר עבור בסיס המשתמשים שלהם. לכן חשוב לזכור שרוב המשתמשים שלכם יראו את הגרסה של שאילתות המאגר של ממשק המשתמש שלכם. ובשביל 10% מהמשתמשים שלא ירגישו שהם לא מרוצים, לא נראה להם שהחוויה שלהם תהיה לא טובה. אם תפעלו לפי האסטרטגיה הזו, במקרה הגרוע ביותר, המשתמשים יראו את ברירת המחדל או את ה'נייד' לגבי חלק מהרכיבים, שהיא לא סוף העולם.

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

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

מבט לעתיד

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

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

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


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

עדכון (2 ביולי 2024): במקור, כל הדוגמאות של קוד ה-CSS כללו קוד Sass (לצורך עקביות עם ההמלצה הסופית). על סמך משוב מהקוראים, שירות ה-CSS הראשון עודכן ל-CSS פשוט, ו-Sass משמש רק בדוגמאות הקוד שבהן נדרש שימוש במיקסנים.