เผยแพร่: 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 หลังจากแยกวิเคราะห์จะมีการเปลี่ยนแปลง 2 อย่างดังนี้
background-color: green;
เลื่อนขึ้นเพื่อรวมเข้ากับประกาศอีก 2 รายการCSSMediaRule
ที่ฝังอยู่ได้รับการเขียนใหม่เพื่อตัดการประกาศในCSSStyleRule
เพิ่มเติมโดยใช้ตัวเลือก&
การเปลี่ยนแปลงทั่วไปอีกอย่างหนึ่งที่คุณจะเห็นที่นี่คือโปรแกรมแยกวิเคราะห์จะทิ้งพร็อพเพอร์ตี้ที่ไม่รองรับ
คุณตรวจสอบ "CSS หลังจากแยกวิเคราะห์" ด้วยตนเองได้โดยอ่าน cssText
จาก CSSStyleRule
ลองใช้เองในพื้นที่ทดสอบแบบอินเทอร์แอกทีฟนี้
Why is this CSS rewritten?
หากต้องการทําความเข้าใจสาเหตุที่เกิดการเขียนใหม่ภายในนี้ คุณต้องเข้าใจวิธีแสดง CSSStyleRule
นี้ใน CSS Object Model (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
มี 2 รายการต่อไปนี้มีความเกี่ยวข้องในกรณีนี้
- พร็อพเพอร์ตี้
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 ตั้งแต่ 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;
}
นโยบายนี้สำคัญกับคุณอย่างไร
ซึ่งหมายความว่าการฝัง CSS มีประสิทธิภาพมากขึ้นมากใน Chrome 130 แต่ก็หมายความว่าคุณจะต้องตรวจสอบโค้ดบางส่วนหากแทรกแซงการประกาศเปล่าด้วยกฎที่ซ้อนกัน
ลองดูตัวอย่างต่อไปนี้ที่ใช้ @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 ระบบจะยกระดับประกาศเหล่านั้น ผลลัพธ์ที่ได้คือประกาศ opacity: 1;
และ scale: 1;
จะไปอยู่ใน CSSStyleRule.style
ตามด้วย CSSStartingStyleRule
(แสดงกฎ @starting-style
) ใน CSSStyleRule.cssRules
ตั้งแต่ Chrome 130 เป็นต้นไป ระบบจะไม่ยกระดับการประกาศอีกต่อไป และคุณจะเห็นออบเจ็กต์ CSSRule
2 รายการที่ฝังอยู่ใน CSSStyleRule.cssRules
ตามลําดับคือ CSSStartingStyleRule
1 รายการ (แสดงกฎ @starting-style
) และ CSSNestedDeclarations
1 รายการที่มีประกาศ 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
}