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

Gerald Monaco
Gerald Monaco

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

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

אפשרויות מתקדמות

טרנספילציה

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

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

לפני
@container (width > 300px) {
  /* content */
}
אחרי
@media all {
  /* content */
}

לפני שאילתות הקונטיינרים, ל-CSS לא הייתה אפשרות להפעיל או להשבית קבוצות כללים באופן שרירותי. כדי למלא את ההתנהגות הזו, יש לשנות גם את הכללים בתוך שאילתת מאגר. לכל @container מוקצה מזהה ייחודי משלו (לדוגמה, 123), שמשמש לשינוי טרנספורמציה של כל סלקטור כך שהוא יחול רק כשיש לרכיב מאפיין cq-XYZ שכולל את המזהה הזה. המאפיין הזה יוגדר על ידי ה-Polyfill בזמן הריצה.

לפני
@container (width > 300px) {
  .card {
    /* ... */
  }
}
אחרי
@media all {
  .card:where([cq-XYZ~="123"]) {
    /* ... */
  }
}

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

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

בהינתן שירות ה-CSS הזה, רכיב עם המחלקה .card צריך תמיד לכלול color: red, מכיוון שהכלל המאוחר יותר תמיד יבטל את הכלל הקודם עם אותו בורר ואותה ספציפיות. לכן, שינוי הכלל הראשון והוספה של בורר מאפיינים נוסף ללא :where(...) יגדיל את הספציפיות ויגרום להחלה שגויה של color: blue.

עם זאת, המחלקה המדומה של :where(...) היא חדשה למדי. בדפדפנים שלא תומכים בו, ה-polyfill מספק פתרון בטוח וקל לעקיפת הבעיה: אפשר בכוונה להגדיל את הספציפיות של הכללים על ידי הוספה ידנית של בורר :not(.container-query-polyfill) דמה לכללי @container:

לפני
@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}
אחרי
@container (width > 300px) {
  .card:not(.container-query-polyfill) {
    color: blue;
  }
}

.card {
  color: red;
}

יש לכך כמה יתרונות:

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

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

פסאודו-אלמנטים

אחת השאלות שאתם שואלים את עצמכם היא: אם ה-Polyfill מגדיר מאפיין cq-XYZ כלשהו באלמנט כך שיכלול את מזהה המאגר הייחודי 123, איך אפשר לתמוך בפסאודו-אלמנטים (שאינן אפשרות להגדיר מאפיינים)?

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

לפני
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
אחרי
@media all {
  #foo:where([cq-XYZ~="123"])::before {
    /* ... */
  }
}

במקום לעבור ל-#foo::before:where([cq-XYZ~="123"]) (שלא יהיה חוקי), הבורר המותנה מועבר לסוף הרכיב המקורי, #foo.

עם זאת, זה לא כל מה שנדרש. למאגר אסור לשנות שום דבר שלא כלול בתוכן (ומאגר לא יכול להיות בתוך עצמו), אבל חשוב לזכור שזה בדיוק מה שהיה קורה אם #foo היה בעצמה את רכיב המאגר שהשאילתה שאילתה עליו. המאפיין #foo[cq-XYZ] ישתנה בטעות וכל הכללים של #foo יוחלו בטעות.

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

לפני
@container (width > 300px) {
  #foo,
  #foo::before {
    /* ... */
  }
}
אחרי
@media all {
  #foo:where([cq-XYZ-A~="123"]),
  #foo:where([cq-XYZ-B~="123"])::before {
    /* ... */
  }
}

מאגר אף פעם לא יחיל את המאפיין הראשון (cq-XYZ-A) על עצמו, ולכן הבורר הראשון יתאים רק אם מאגר הורה אחר עמד בתנאי המאגר והחיל אותו.

מספר היחידות היחסיות של הקונטיינר

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

לפני
.card {
  width: 10cqw;
  height: 10cqh;
}
אחרי
.card {
  width: calc(10 * --cq-XYZ-cqw);
  height: calc(10 * --cq-XYZ-cqh);
}

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

/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);

/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);

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

תכונות

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

לפני
.card {
  container-name: card-container;
  container-type: inline-size;
}
אחרי
.card {
  --cq-XYZ-container-name: card-container;
  --cq-XYZ-container-type: inline-size;
}

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

לפני
@supports (container-type: inline-size) {
  /* ... */
}
אחרי
@supports (--cq-XYZ-container-type: inline-size) {
  /* ... */
}

כברירת מחדל, נכסי CSS מותאמים אישית עוברים בירושה. כלומר, כל צאצא של .card יקבל את הערך --cq-XYZ-container-name ו---cq-XYZ-container-type. זה בהחלט לא המצב של הנכסים המותאמים. כדי לפתור זאת, ה-polyfill יוסיף את הכלל הבא לפני סגנונות משתמש כלשהם, ויבטיח שכל רכיב יקבל את הערכים הראשוניים, אלא אם כן כלל אחר שינה אותו באופן מכוון.

* {
  --cq-XYZ-container-name: none;
  --cq-XYZ-container-type: normal;
}

שיטות מומלצות

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

במהלך הטעינה הראשונית, קורים דברים רבים לפני שה-polyfill יכול לפרוס את הדף:

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

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

כדי להקל עליכם להעניק למבקרים חוויית שימוש נעימה, ה-Polyfill נועד לתת עדיפות לעיכוב בקלט הראשון (FID) ולCumulative Layout Shift (CLS), אולי על חשבון המהירות שבה נטען רכיב התוכן הכי גדול (LCP). ליתר ביטחון, ה-polyfill לא מבטיח ששאילתות הקונטיינרים ייבדקו לפני הצביעה הראשונה. כלומר, כדי שחוויית המשתמש תהיה הטובה ביותר, חובה לוודא שכל תוכן שהגודל או המיקום שלו יושפעו מהשימוש בשאילתות של קונטיינרים יוסתר עד שה-polyfill יהיה נטען ויבצע טרנספורמציה של ה-CSS. דרך אחת לעשות זאת היא באמצעות כלל @supports:

@supports not (container-type: inline-size) {
  #content {
    visibility: hidden;
  }
}

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

מומלץ להשתמש בגישה הזו מכמה סיבות:

  • טוען CSS טהור מצמצם את התקורה על משתמשים עם דפדפנים חדשים יותר, ומספק משוב קומפקטי למשתמשים בדפדפנים ישנים וברשתות איטיות יותר.
  • שילוב המיקום המוחלט של המטען עם visibility: hidden מונע שינוי בפריסה.
  • לאחר טעינת ה-Polyfill, התנאי @supports הזה יפסיק לעבור, והתוכן שלך ייחשף.
  • בדפדפנים עם תמיכה מובנית בשאילתות של קונטיינרים, התנאי אף פעם לא יעבור, ולכן הדף יוצג בצבע הראשון כמצופה.

סיכום

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

אנחנו כבר מחכים לראות ולחוות את הדברים המדהימים שתוכלו ליצור בעזרתה.

אישורים

תמונה ראשית (Hero) מאת דן כריסטיאן פדור, ב-UnFlood.