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

Gerald Monaco
Gerald Monaco

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

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

מה צריך לעשות

תרגום

כשמנתח ה-CSS בתוך דפדפן נתקל בכלל at לא ידוע, כמו כלל @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.
  • הספציפיות של הכללים תהיה תמיד זהה, מאחר שה-Popolyfill לא משנה אותו.

במהלך ההמרה, ה-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) ולשינוי פריסה מצטבר (CLS), אולי על חשבון המהירות שבה נטען רכיב התוכן הכי גדול (LCP). באופן קונקרטי, ה-polyfill לא מבטיח ששאילתות הקונטיינר יעברו הערכה לפני העיבוד הראשון. כלומר, כדי ליהנות מחוויית המשתמש הטובה ביותר, חובה לוודא שכל תוכן שהגודל או המיקום שלו יושפעו מהשימוש בשאילתות של מאגרי תגים יוסתר עד שה-polyfill נטען ויבצע טרנספורמציה של ה-CSS. אפשר לעשות זאת באמצעות כלל @supports:

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

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

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

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

סיכום

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

אנחנו מצפים בקוצר רוח לראות ולחוות את הדברים המדהימים שתצרו איתו.

אישורים

תמונה ראשית (Hero) של Dan Cristian Pâdure לקרוא ב-Unbounce.