เผยแพร่: 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 }