ดูวิธีใช้ @scope เพื่อเลือกองค์ประกอบภายในซับต้นไม้ที่จำกัดของ DOM เท่านั้น
ศิลปะอันละเอียดอ่อนในการเขียนตัวเลือก CSS
เมื่อเขียนตัวเลือก คุณอาจพบว่าตัวเองต้องเลือกระหว่าง 2 โลก ในทางหนึ่ง คุณควรเลือกองค์ประกอบให้เจาะจง ในทางกลับกัน คุณก็ต้องการให้ตัวเลือกยังคงลบล้างได้ง่ายและไม่ได้เชื่อมโยงกับโครงสร้าง DOM อย่างแน่นหนา
เช่น เมื่อคุณต้องการเลือก "รูปภาพหลักในพื้นที่เนื้อหาของคอมโพเนนต์การ์ด" ซึ่งเป็นการเลือกองค์ประกอบที่ค่อนข้างเฉพาะเจาะจง คุณอาจไม่ต้องการเขียนตัวเลือกอย่าง .card > .content > img.hero
- ตัวเลือกนี้มีความเฉพาะเจาะจงสูงพอสมควรที่
(0,3,1)
ซึ่งทําให้การลบล้างยากขึ้นเมื่อโค้ดมีความยาวมากขึ้น - การใช้คอมบิเนเตอร์รายการย่อยโดยตรงจะเชื่อมโยงกับโครงสร้าง DOM อย่างแน่นหนา หากมาร์กอัปมีการเปลี่ยนแปลง คุณจะต้องเปลี่ยน CSS ด้วย
แต่คุณก็ไม่ต้องการเขียนแค่ img
เป็นตัวเลือกสําหรับองค์ประกอบนั้น เนื่องจากจะเลือกองค์ประกอบรูปภาพทั้งหมดในหน้าเว็บ
การหาสมดุลที่เหมาะสมในเรื่องนี้มักเป็นเรื่องท้าทาย ในช่วงหลายปีที่ผ่านมา นักพัฒนาแอปบางรายได้คิดค้นวิธีแก้ปัญหาและวิธีแก้ปัญหาชั่วคราวเพื่อช่วยคุณในสถานการณ์เช่นนี้ เช่น
- วิธีการต่างๆ เช่น BEM กําหนดให้คุณกําหนดคลาส
card__img card__img--hero
ให้กับองค์ประกอบนั้นเพื่อลดความเฉพาะเจาะจง ในขณะที่ให้คุณเลือกองค์ประกอบที่เฉพาะเจาะจงได้ - โซลูชันที่ใช้ JavaScript เช่น CSS แบบจำกัดขอบเขตหรือคอมโพเนนต์ที่มีสไตล์จะเขียนตัวเลือกทั้งหมดของคุณใหม่โดยการเพิ่มสตริงที่สร้างขึ้นแบบสุ่ม เช่น
sc-596d7e0e-4
ลงในตัวเลือกเพื่อป้องกันไม่ให้ตัวเลือกกำหนดเป้าหมายองค์ประกอบที่อีกฝั่งหนึ่งของหน้า - บางไลบรารียังยกเลิกการใช้ตัวเลือกไปเลยและกำหนดให้คุณใส่ทริกเกอร์การจัดสไตล์ในมาร์กอัปโดยตรง
แต่จะเกิดอะไรขึ้นหากคุณไม่ต้องการสิ่งเหล่านี้ จะเกิดอะไรขึ้นหาก CSS มีวิธีให้คุณเลือกองค์ประกอบได้อย่างเจาะจงโดยไม่ต้องเขียนตัวเลือกที่เฉพาะเจาะจงสูงหรือตัวเลือกที่เชื่อมโยงกับ DOM อย่างแน่นหนา ด้วยเหตุนี้ @scope
จึงเข้ามามีบทบาท โดยให้คุณเลือกองค์ประกอบได้ภายในซับต้นไม้ของ DOM เท่านั้น
ขอแนะนำ @scope
@scope
ช่วยให้คุณจํากัดการเข้าถึงของตัวเลือกได้ ซึ่งทำได้โดยการตั้งค่า scoping root ซึ่งกําหนดขอบเขตบนของต้นไม้ย่อยที่คุณต้องการกําหนดเป้าหมาย เมื่อตั้งค่ารูทการกําหนดขอบเขตแล้ว กฎสไตล์ที่มีชื่อเรียกว่ากฎสไตล์ที่กําหนดขอบเขตจะเลือกได้จากซับต้นไม้ที่จำกัดของ DOM เท่านั้น
เช่น หากต้องการกำหนดเป้าหมายเฉพาะองค์ประกอบ <img>
ในคอมโพเนนต์ .card
ให้ตั้งค่า .card
เป็นรูทการกําหนดขอบเขตของ at-rule @scope
@scope (.card) {
img {
border-color: green;
}
}
กฎรูปแบบที่มีขอบเขต img { … }
จะเลือกได้เฉพาะองค์ประกอบ <img>
ที่อยู่ในขอบเขตขององค์ประกอบ .card
ที่ตรงกัน
หากไม่ต้องการให้ระบบเลือกองค์ประกอบ <img>
ภายในพื้นที่เนื้อหาของการ์ด (.card__content
) ให้ทําการเลือก img
ให้เฉพาะเจาะจงมากขึ้น อีกวิธีหนึ่งคือใช้ประโยชน์จากข้อเท็จจริงที่ว่า@scope
at-rule ยังยอมรับขีดจํากัดการกําหนดขอบเขตด้วย ซึ่งจะเป็นตัวกําหนดขอบเขตล่าง
@scope (.card) to (.card__content) {
img {
border-color: green;
}
}
กฎรูปแบบที่มีขอบเขตนี้จะกำหนดเป้าหมายเฉพาะองค์ประกอบ <img>
ที่วางไว้ระหว่างองค์ประกอบ .card
กับ .card__content
ในลําดับชั้นบรรพบุรุษ การกําหนดขอบเขตประเภทนี้ซึ่งมีขอบเขตบนและล่างมักเรียกว่าขอบเขตโดนัท
ตัวเลือก :scope
โดยค่าเริ่มต้น กฎรูปแบบที่มีขอบเขตทั้งหมดจะสัมพันธ์กับรูทการกําหนดขอบเขต นอกจากนี้ คุณยังกําหนดเป้าหมายองค์ประกอบรูทการกําหนดขอบเขตเองได้ด้วย ในกรณีนี้ ให้ใช้ตัวเลือก :scope
@scope (.card) {
:scope {
/* Selects the matched .card itself */
}
img {
/* Selects img elements that are a child of .card */
}
}
ระบบจะใส่ :scope
ไว้ข้างหน้าตัวเลือกภายในกฎสไตล์ที่มีขอบเขตโดยปริยาย หากต้องการ คุณก็ระบุข้อมูลอย่างชัดเจนได้โดยการใส่ :scope
ไว้ข้างหน้า หรือจะใส่ตัวเลือก &
ไว้ข้างหน้าก็ได้จากการฝัง CSS
@scope (.card) {
img {
/* Selects img elements that are a child of .card */
}
:scope img {
/* Also selects img elements that are a child of .card */
}
& img {
/* Also selects img elements that are a child of .card */
}
}
ขีดจํากัดการกําหนดขอบเขตสามารถใช้คลาสจำลอง :scope
เพื่อกำหนดความสัมพันธ์ที่เฉพาะเจาะจงกับรูทการกําหนดขอบเขตได้ ดังนี้
/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }
นอกจากนี้ ขีดจํากัดการกําหนดขอบเขตยังอ้างอิงองค์ประกอบที่อยู่นอกรูทการกําหนดขอบเขตได้ด้วยโดยใช้ :scope
เช่น
/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }
โปรดทราบว่ากฎสไตล์ที่มีขอบเขตจะหนีออกจากซับต้นไม้ไม่ได้ การเลือกอย่าง :scope + p
ไม่ถูกต้องเนื่องจากพยายามเลือกองค์ประกอบที่ไม่ได้อยู่ในขอบเขต
@scope
และความละเอียด
ตัวเลือกที่คุณใช้ในช่วงเกริ่นนำสำหรับ @scope
จะไม่ส่งผลต่อความเฉพาะเจาะจงของตัวเลือกที่มี ในตัวอย่างนี้ ตัวเลือก img
ยังคงมีความเฉพาะเจาะจงเป็น (0,0,1)
@scope (#sidebar) {
img { /* Specificity = (0,0,1) */
…
}
}
ความเฉพาะเจาะจงของ :scope
นั้นเหมือนกับของคลาสจำลองทั่วไป ซึ่งก็คือ (0,1,0)
@scope (#sidebar) {
:scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
…
}
}
ในตัวอย่างนี้ &
ได้รับการเขียนใหม่ภายในเป็นตัวเลือกที่ใช้สำหรับรูทการกําหนดขอบเขต ซึ่งรวมอยู่ในตัวเลือก :is()
สุดท้ายเบราว์เซอร์จะใช้ :is(#sidebar, .card) img
เป็นตัวเลือกในการจับคู่ กระบวนการนี้เรียกว่าการกรองน้ำตาลออก
@scope (#sidebar, .card) {
& img { /* desugars to `:is(#sidebar, .card) img` */
…
}
}
เนื่องจาก &
ได้รับการกรองน้ำตาลออกโดยใช้ :is()
ระบบจะคำนวณความเฉพาะเจาะจงของ &
ตามกฎความเฉพาะเจาะจงของ :is()
กล่าวคือ ความเฉพาะเจาะจงของ &
จะเป็นความเฉพาะเจาะจงของอาร์กิวเมนต์ที่เจาะจงที่สุด
เมื่อนําไปใช้กับตัวอย่างนี้ ความเฉพาะเจาะจงของ :is(#sidebar, .card)
คืออาร์กิวเมนต์ที่เฉพาะเจาะจงที่สุด ซึ่งก็คือ #sidebar
และจึงกลายเป็น (1,0,0)
รวมเข้ากับความเฉพาะเจาะจงของ img
ซึ่งก็คือ (0,0,1)
คุณจะได้ (1,0,1)
ที่เป็นระดับความเฉพาะเจาะจงของตัวเลือกที่ซับซ้อนทั้งรายการ
@scope (#sidebar, .card) {
& img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
…
}
}
ความแตกต่างระหว่าง :scope
กับ &
ภายใน @scope
นอกจากความแตกต่างในวิธีคํานวณความเฉพาะเจาะจงแล้ว ความแตกต่างอีกอย่างหนึ่งระหว่าง :scope
กับ &
คือ :scope
แสดงถึงรูทการกําหนดขอบเขตที่ตรงกัน ขณะที่ &
แสดงถึงตัวเลือกที่ใช้จับคู่รูทการกําหนดขอบเขต
ด้วยเหตุนี้ คุณจึงใช้ &
ได้หลายครั้ง ซึ่งต่างจาก :scope
ที่คุณใช้ได้เพียงครั้งเดียว เนื่องจากคุณจับคู่รูทการกําหนดขอบเขตภายในรูทการกําหนดขอบเขตไม่ได้
@scope (.card) {
& & { /* Selects a `.card` in the matched root .card */
}
:scope :scope { /* ❌ Does not work */
…
}
}
ขอบเขตที่ไม่มีช่วงเกริ่นนำ
เมื่อเขียนรูปแบบในบรรทัดด้วยองค์ประกอบ <style>
คุณสามารถกําหนดขอบเขตกฎรูปแบบให้กับองค์ประกอบหลักที่ล้อมรอบองค์ประกอบ <style>
ได้โดยไม่ได้ระบุรูทการกําหนดขอบเขต โดยตัดช่วงเกริ่นนำของ @scope
ออก
<div class="card">
<div class="card__header">
<style>
@scope {
img {
border-color: green;
}
}
</style>
<h1>Card Title</h1>
<img src="…" height="32" class="hero">
</div>
<div class="card__content">
<p><img src="…" height="32"></p>
</div>
</div>
ในตัวอย่างข้างต้น กฎที่มีขอบเขตจะกำหนดเป้าหมายเฉพาะองค์ประกอบภายใน div
ที่มีชื่อคลาส card__header
เนื่องจาก div
นั้นเป็นองค์ประกอบหลักขององค์ประกอบ <style>
@scope ในการแสดงผลตามลำดับขั้น
ภายใน CSS Cascade @scope
ยังเพิ่มเกณฑ์ใหม่ด้วย ได้แก่ ความใกล้เคียงของการกําหนดขอบเขต ขั้นตอนจะอยู่หลังความเฉพาะเจาะจง แต่อยู่ก่อนลําดับการแสดง
เมื่อเปรียบเทียบการประกาศที่ปรากฏในกฎสไตล์ที่มีรูทการกําหนดขอบเขตต่างกัน การประกาศที่มีการข้ามองค์ประกอบรุ่นหรือองค์ประกอบพี่น้องน้อยที่สุดระหว่างรูทการกําหนดขอบเขตกับเรื่องของกฎสไตล์ที่มีขอบเขตจะเป็นผู้ชนะ
ขั้นตอนใหม่นี้จะมีประโยชน์เมื่อคุณฝังคอมโพเนนต์หลายรูปแบบ มาดูตัวอย่างนี้ซึ่งยังไม่ได้ใช้ @scope
<style>
.light { background: #ccc; }
.dark { background: #333; }
.light a { color: black; }
.dark a { color: white; }
</style>
<div class="light">
<p><a href="#">What color am I?</a></p>
<div class="dark">
<p><a href="#">What about me?</a></p>
<div class="light">
<p><a href="#">Am I the same as the first?</a></p>
</div>
</div>
</div>
เมื่อดูมาร์กอัปส่วนนั้น ลิงก์ที่ 3 จะเป็น white
แทนที่จะเป็น black
แม้ว่าจะเป็นลิงก์ย่อยของ div
ที่มีการใช้คลาส .light
ก็ตาม สาเหตุคือเกณฑ์ลําดับการแสดงผลที่แคสเคดใช้ที่นี่เพื่อระบุผู้ชนะ เนื่องจากระบบเห็นว่า .dark a
ได้รับการประกาศเป็นอันดับสุดท้าย จึงจะชนะตามกฎ .light a
เกณฑ์การกําหนดขอบเขตความใกล้เคียงช่วยแก้ปัญหานี้ได้
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
เนื่องจากตัวเลือก a
ที่มีขอบเขตทั้ง 2 รายการมีความเฉพาะเจาะจงเหมือนกัน เกณฑ์ความใกล้เคียงของขอบเขตจึงเริ่มทำงาน โดยจะพิจารณาทั้ง 2 ตัวเลือกตามระดับความใกล้กับรูทการกําหนดขอบเขต สําหรับองค์ประกอบ a
ลำดับที่ 3 นั้น องค์ประกอบดังกล่าวจะอยู่ที่ระดับ 1 ฮ็อปจากรูทการกําหนดขอบเขต .light
แต่อยู่ที่ระดับ 2 ฮ็อปจากรูทการกําหนดขอบเขต .dark
ดังนั้น ตัวเลือก a
ใน .light
จะเป็นผู้ชนะ
หมายเหตุปิดท้าย: การแยกตัวเลือก ไม่ใช่การแยกสไตล์
สิ่งที่ควรทราบอย่างหนึ่งคือ @scope
จะจํากัดการเข้าถึงของตัวเลือก แต่ไม่แยกสไตล์ พร็อพเพอร์ตี้ที่รับค่าไปยังพร็อพเพอร์ตี้ย่อยจะยังคงรับค่าอยู่ นอกเหนือจากขีดจำกัดล่างของ @scope
พร็อพเพอร์ตี้หนึ่งๆ ดังกล่าวคือพร็อพเพอร์ตี้ color
เมื่อประกาศ color
ภายในขอบเขตโดนัท color
จะยังคงรับค่าจากองค์ประกอบย่อยภายในรูของโดนัท
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
ในตัวอย่างด้านบน องค์ประกอบ .card__content
และองค์ประกอบย่อยมีสี hotpink
เนื่องจากรับค่ามาจาก .card
(ภาพปกโดย rustam burkhanov ใน Unsplash)