תאריך פרסום: 8 באוקטובר 2024
כדי לתקן כמה בעיות מוזרות שקשורות להטמעת CSS, קבוצת העבודה בנושא CSS החליטה להוסיף את הממשק CSSNestedDeclarations
למפרט ההטמעה של CSS. בעקבות ההוספה הזו, הצהרות שמופיעות אחרי כללי סגנון לא מוסטות למעלה, בין שיפורים נוספים.
השינויים האלה זמינים ב-Chrome מגרסה 130 ומוכנים לבדיקה ב-Firefox Nightly 132 וב-Safari Technology Preview 204.
תמיכה בדפדפנים
הבעיה בהטמעת CSS ללא CSSNestedDeclarations
אחד ממלכודות ההטמעה של CSS הוא שבמקור, קטע הקוד הבא לא פועל כצפוי:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
אם תעיינו בקוד, תוכלו להניח שלרכיב <div class=foo>
יש green
background-color
כי ההצהרה על background-color: green;
מופיעה בסוף. עם זאת, זה לא המצב ב-Chrome לפני גרסה 130. בגרסאות האלה, שבהן אין תמיכה ב-CSSNestedDeclarations
, הערך של background-color
ברכיב הוא red
.
לאחר ניתוח הכלל בפועל של Chrome לפני 130 שימושים, הוא מבצע את הפעולות הבאות:
.foo {
width: fit-content;
background-color: green;
@media screen {
& {
background-color: red;
}
}
}
קובץ ה-CSS אחרי הניתוח עבר שני שינויים:
- ה-
background-color: green;
הועבר למעלה כדי להצטרף לשתי ההצהרות האחרות. - השדה
CSSMediaRule
שהוצב בו שכתב נכתב כדי לכווץ את ההצהרות שלו בCSSStyleRule
נוסף באמצעות הבורר&
.
שינוי אופייני נוסף שרואים כאן הוא שהמנתח משאיר מאחור מאפיינים שהוא לא תומך בהם.
אפשר לבדוק בעצמכם את ה-CSS אחרי הניתוח על ידי קריאה חוזרת של cssText
מ-CSSStyleRule
.
אתם יכולים לנסות זאת בעצמכם בגן המשחקים האינטראקטיבי הזה:
למה קובץ ה-CSS הזה נכתב מחדש?
כדי להבין למה התרחשה הכתיבה מחדש הפנימית הזו, צריך להבין איך ה-CSSStyleRule
הזה מיוצג במודל האובייקטים של CSS (CSSOM).
ב-Chrome בגרסאות קודמות ל-130, קטע ה-CSS ששיתפתם מקודם עובר סריאליזציה לקוד הבא:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
מבין כל המאפיינים של CSSStyleRule
, שני המאפיינים הבאים רלוונטיים במקרה הזה:
- הנכס
style
, שהוא מופע שלCSSStyleDeclaration
שמייצג את ההצהרות. - המאפיין
cssRules
, שהואCSSRuleList
שמכיל את כל אובייקטי ה-CSSRule
בתצוגת עץ.
מאחר שכל ההצהרות מקודק ה-CSS מסתיימות בנכס style
של CSStyleRule
, יש אובדן מידע. בבדיקת המאפיין style
לא ברור שה-background-color: green
הוצהר אחרי CSSMediaRule
המקונן.
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ …
זו בעיה, כי כדי שמנוע CSS יפעל כמו שצריך, הוא צריך להיות מסוגל להבדיל בין מאפיינים שמופיעים בתחילת התוכן של כלל סגנון לבין מאפיינים שמופיעים בין כללים אחרים.
לגבי ההצהרות בתוך ה-CSSMediaRule
שמקובצות פתאום ב-CSSStyleRule
: הסיבה לכך היא שה-CSSMediaRule
לא תוכנן להכיל הצהרות.
בגלל ש-CSSMediaRule
יכול להכיל כללים בתצוגת עץ, שאפשר לגשת אליהם דרך המאפיין cssRules
, ההצהרות מוקף באופן אוטומטי ב-CSSStyleRule
.
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
איך פותרים את הבעיה הזו?
קבוצת העבודה של שירות ה-CSS בדקה כמה אפשרויות לפתרון הבעיה הזו.
אחד מהפתרונות המוצעים היה לכווץ את כל ההצהרות הבסיסיות בתוך CSSStyleRule
בתוך בורר בתוך בורר (&
). הרעיון הזה נמחק מסיבות שונות, כולל תופעות הלוואי הלא רצויות הבאות של הסרת &
כתוצאה מ:is(…)
:
- יש לה השפעה על הספציפיות. הסיבה לכך היא ש-
:is()
מקבל את הספציפיות של הארגומנט הספציפי ביותר שלו. - הוא לא פועל טוב עם פסאודו-רכיבים בסלקטורים החיצוניים המקוריים. הסיבה לכך היא ש-
:is()
לא מקבל פסאודו-רכיבים בארגומנט של רשימת הבוררים.
לדוגמה:
#foo, .foo, .foo::before {
width: fit-content;
background-color: red;
@media screen {
background-color: green;
}
}
לאחר ניתוח קטע הקוד הזה, הוא הופך לזה ב-Chrome לפני גרסה 130:
#foo,
.foo,
.foo::before {
width: fit-content;
background-color: red;
@media screen {
& {
background-color: green;
}
}
}
זו בעיה כי ה-CSSRule
בתצוגת עץ עם הבורר &
:
- המערכת מפחיתה את הנתונים ל-
:is(#foo, .foo)
, ומוציאה את.foo::before
מרשימת הבוררים. - הוא ספציפי ל-
(1,0,0)
ולכן קשה יותר להחליף אותו מאוחר יותר.
אפשר לבדוק את זה על ידי בדיקת הפורמט שאליו הכלל עובר סריאליזציה:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "#foo, .foo, .foo::before"
.resolvedSelectorText = "#foo, .foo, .foo::before"
.specificity = (1,0,0),(0,1,0),(0,1,1)
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: red
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(#foo, .foo, .foo::before)"
.specificity = (1,0,0)
.style (CSSStyleDeclaration, 1) =
- background-color: green
מבחינה חזותית, המשמעות היא גם שהערך של background-color
ב-.foo::before
הוא red
במקום green
.
גישה נוספת שקבוצת העבודה של CSS בחנה היא להקיף את כל ההצהרות המוטמעות בכלל @nest
. הבקשה נדחתה בגלל שהיא עלולה לגרום לנסיגה בחוויית המפתחים.
אנחנו גאים להציג את הממשק CSSNestedDeclarations
הפתרון שאליו הגיעה קבוצת העבודה בנושא CSS הוא הצגת כלל ההצהרות בתצוגת עץ.
הכלל הזה לגבי הצהרות בתצוגת עץ מיושם ב-Chrome החל מגרסה 130.
תמיכה בדפדפנים
ההוספה של הכלל לגבי הצהרות בתצוגת עץ משנה את האופן שבו מתבצע הניתוח של קובצי ה-CSS, כך שהמערכת תארוז באופן אוטומטי הצהרות רצופות בתצוגת עץ ישירה במכונה מסוג CSSNestedDeclarations
. כשממיינים את המאפיין, המכונה של CSSNestedDeclarations
מופיעה במאפיין cssRules
של CSSStyleRule
.
שוב, ניקח לדוגמה את CSSStyleRule
הבא:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
כשממיינים בסדרה ב-Chrome 130 ואילך, זה נראה כך:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = (0,1,0)
.style (CSSStyleDeclaration, 1) =
- width: fit-content
.cssRules (CSSRuleList, 2) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: red
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: green
בגלל שהכלל CSSNestedDeclarations
מסתיים ב-CSSRuleList
, המנתח יכול לשמור על המיקום של הצהרת background-color: green
: אחרי ההצהרה background-color: red
(ששייכת ל-CSSMediaRule
).
בנוסף, שימוש במופע CSSNestedDeclarations
לא גורם לתופעות לוואי לא נעימות כמו אלה שנגרמו מהפתרונות האחרים, שכבר לא רלוונטיים: כלל ההצהרות המוטמעות תואם לרכיבים ולפסאודו-רכיבים בדיוק כמו כלל הסגנון ההורה שלו, עם אותה התנהגות ספציפיות.
הוכחה לכך היא קריאה של cssText
של CSSStyleRule
. בזכות הכלל של ההצהרות בתצוגת עץ, הוא זהה ל-CSS שהזנתם:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
משמעות המדיניות מבחינתכם
המשמעות היא שהחל מגרסה 130 של Chrome, ההטמעה של CSS בתוך CSS השתפרה משמעותית. אבל, המשמעות היא גם שיכול להיות שתצטרכו לעבור על חלק מהקוד אם שילוב של הצהרות בסיסיות עם כללים בתצוגת עץ.
בדוגמה הבאה נעשה שימוש בפונקציה הנהדרת @starting-style
/* This does not work in Chrome 130 */
#mypopover:popover-open {
@starting-style {
opacity: 0;
scale: 0.5;
}
opacity: 1;
scale: 1;
}
לפני Chrome 130, ההצהרות האלה היו מועברות (hoisted). בסיום, ההצהרות opacity: 1;
ו-scale: 1;
ייכנסו ל-CSSStyleRule.style
, ולאחר מכן CSSStartingStyleRule
(שמייצג את הכלל @starting-style
) ב-CSSStyleRule.cssRules
.
החל מגרסת Chrome 130, ההצהרות לא מועברות יותר לחלק העליון של הקוד, ובסופו של דבר נוצרים שני אובייקטים בתצוגת עץ של CSSRule
ב-CSSStyleRule.cssRules
. לפי הסדר: CSSStartingStyleRule
אחד (שמייצג את הכלל @starting-style
) ו-CSSNestedDeclarations
אחד שמכיל את ההצהרות של opacity: 1; scale: 1;
.
בגלל השינוי הזה, ההצהרות @starting-style
מוחלפות על ידי ההצהרות שנכללות במכונה CSSNestedDeclarations
, וכך מסירה את האנימציה של הרשומה.
כדי לתקן את הקוד, צריך לוודא שהבלוק @starting-style
מופיע אחרי ההצהרות הרגילות. למשל:
/* This works in Chrome 130 */
#mypopover:popover-open {
opacity: 1;
scale: 1;
@starting-style {
opacity: 0;
scale: 0.5;
}
}
אם משאירים את ההצהרות בתצוגת עץ מעל הכללים בתצוגת עץ כשמשתמשים בתצוגת עץ של CSS, הקוד יפעל בדרך כלל בצורה תקינה בכל הגרסאות של כל הדפדפנים שתומכים בתצוגת עץ של CSS.
לסיום, כדי לזהות את הזמינות של CSSNestedDeclarations
, אפשר להשתמש בקטע הקוד הבא של JavaScript:
if (!("CSSNestedDeclarations" in self && "style" in CSSNestedDeclarations.prototype)) {
// CSSNestedDeclarations is not available
}