שאילתות מאגר הן תכונה חדשה ב-CSS שמאפשרת לכתוב לוגיקה של עיצוב שמטרגטת מאפיינים של רכיב הורה (לדוגמה, הרוחב או הגובה שלו) כדי לעצב את הצאצאים שלו. לאחרונה פורסם עדכון גדול ל-polyfill, במקביל להוספת התמיכה בדפדפנים.
במאמר הזה נסביר איך פוליפיל (polyfill) פועל, אילו אתגרים הוא עוזר להתגבר עליהם ואילו שיטות מומלצות יש להשתמש בהן כדי לספק למבקרים חוויית משתמש מעולה.
מאחורי הקלעים
טרנספיליציה
כשמנתח ה-CSS בדפדפן נתקל בכלל at-rule לא ידוע, כמו הכלל החדש @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, ייתכן שהמדדים הבסיסיים של חוויית המשתמש (Core Web Vitals) שלכם יירדו.
כדי להקל עליכם לספק למבקרים חוויה נעימה, ה-polyfill תוכנן לתת עדיפות להשהיה לאחר קלט ראשוני (FID) ולמדד יציבות חזותית (CLS), על חשבון המהירות שבה נטען רכיב התוכן הכי גדול (LCP). באופן ספציפי, ה-polyfill לא מבטיח שהשאילתות של הקונטיינר ייבדקו לפני הצגת התמונה הראשונית. כלומר, כדי לספק את חוויית המשתמש הטובה ביותר, חובה לוודא שכל תוכן שהגודל או המיקום שלו יושפעו משימוש בשאילתות מאגר יהיה מוסתר עד שה-polyfill יטמיע ויתרגם את ה-CSS. אחת מהדרכים לעשות זאת היא באמצעות כלל @supports
:
@supports not (container-type: inline-size) {
#content {
visibility: hidden;
}
}
מומלץ לשלב את האנימציה הזו עם אנימציית טעינה טהורה ב-CSS, שממוקמת באופן מוחלט מעל התוכן (המוסתר) כדי להודיע למבקר שמשהו קורה. כאן אפשר למצוא הדגמה מלאה של הגישה הזו.
מומלץ להשתמש בגישה הזו מכמה סיבות:
- טוען CSS טהור מקטין את התקורה למשתמשים עם דפדפנים חדשים, ומספק משוב קל למשתמשים עם דפדפנים ישנים יותר ורשתות איטיות יותר.
- שילוב של מיקום מוחלט של הטען עם
visibility: hidden
ימנע שינויי פריסה. - אחרי שה-polyfill נטען, התנאי
@supports
יפסיק להתקיים והתוכן יוצג. - בדפדפנים עם תמיכה מובנית בשאילתות של קונטיינרים, התנאי לא יתקיים אף פעם, ולכן הדף יוצג בהצגת התוכן הראשונית כצפוי.
סיכום
אם אתם רוצים להשתמש בשאילתות של קונטיינרים בדפדפנים ישנים יותר, כדאי לנסות את ה-polyfill. אם נתקלתם בבעיות, אתם יכולים לדווח על הבעיה.
אנחנו כבר ממש סקרנים לראות את הדברים המדהימים שתיצרו בעזרתו.