ดูวิธีใช้ @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 ใน Cascade
ภายใน 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 รายการมีความเฉพาะเจาะจงเหมือนกัน เกณฑ์ความใกล้เคียงของขอบเขตจึงเริ่มทำงาน จะให้น้ำหนักของตัวเลือกทั้งสองตามระยะห่างจากรากที่กำหนดขอบเขต สําหรับองค์ประกอบ 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)