ดูวิธีใช้ @scope เพื่อเลือกองค์ประกอบภายใน Subtree ที่จำกัดของ DOM เท่านั้น
เผยแพร่: 4 ตุลาคม 2023
เมื่อเขียนตัวเลือก คุณอาจพบว่าตัวเองอยู่ระหว่าง 2 โลก ในอีกด้านหนึ่ง คุณก็ต้องการระบุองค์ประกอบที่เลือกอย่างชัดเจน ในทางกลับกัน คุณต้องการให้ตัวเลือกยังคงลบล้างได้ง่ายและไม่เชื่อมโยงกับโครงสร้าง DOM อย่างแน่นหนา
เช่น เมื่อต้องการเลือก "รูปภาพฮีโร่ในพื้นที่เนื้อหาของคอมโพเนนต์การ์ด" ซึ่งเป็นการเลือกองค์ประกอบที่ค่อนข้างเฉพาะเจาะจง คุณคงไม่ต้องการเขียนตัวเลือกอย่าง .card > .content > img.hero
- ตัวเลือกนี้มีความเฉพาะเจาะจงค่อนข้างสูงที่
(0,3,1)ซึ่งทำให้การลบล้างเป็นเรื่องยากเมื่อโค้ดของคุณเติบโตขึ้น - การใช้ตัวเลือกองค์ประกอบย่อยโดยตรงทำให้เชื่อมโยงกับโครงสร้าง DOM อย่างใกล้ชิด หากมาร์กอัปมีการเปลี่ยนแปลง คุณจะต้องเปลี่ยน CSS ด้วย
แต่คุณก็ไม่ควรเขียนแค่ img เป็นตัวเลือกสำหรับองค์ประกอบนั้น เนื่องจากจะเลือกองค์ประกอบรูปภาพทั้งหมดในหน้าเว็บ
การหาสมดุลที่เหมาะสมในเรื่องนี้มักเป็นเรื่องที่ท้าทาย ในช่วงหลายปีที่ผ่านมา นักพัฒนาแอปบางรายได้คิดค้นโซลูชันและวิธีแก้ปัญหาเบื้องต้นเพื่อช่วยคุณในสถานการณ์เช่นนี้ เช่น
- วิธีการต่างๆ เช่น BEM กำหนดให้คุณกำหนดคลาส
card__img card__img--heroให้กับองค์ประกอบนั้นเพื่อคงความเฉพาะเจาะจงไว้ในระดับต่ำ ขณะเดียวกันก็ช่วยให้คุณเลือกได้อย่างเฉพาะเจาะจง - โซลูชันที่ใช้ JavaScript เช่น Scoped CSS หรือ Styled Components จะเขียนตัวเลือกทั้งหมดใหม่โดยการเพิ่มสตริงที่สร้างขึ้นแบบสุ่ม เช่น
sc-596d7e0e-4ลงในตัวเลือกเพื่อป้องกันไม่ให้ตัวเลือกกำหนดเป้าหมายองค์ประกอบที่อีกด้านหนึ่งของหน้าเว็บ - ห้องสมุดบางแห่งถึงกับยกเลิกตัวเลือกทั้งหมดและกำหนดให้คุณใส่ทริกเกอร์การจัดรูปแบบในมาร์กอัปโดยตรง
แต่หากคุณไม่ต้องการสิ่งใดเลย จะเกิดอะไรขึ้นหาก CSS มีวิธีที่ช่วยให้คุณระบุองค์ประกอบที่เลือกได้อย่างแม่นยำโดยไม่ต้องเขียนตัวเลือกที่มีความเฉพาะเจาะจงสูงหรือตัวเลือกที่เชื่อมโยงกับ DOM อย่างใกล้ชิด @scope จึงเข้ามามีบทบาทในจุดนี้ โดยเป็นวิธีให้คุณเลือกองค์ประกอบเฉพาะภายใน Subtree ของ DOM
ขอแนะนำ @scope
@scope ช่วยให้คุณจำกัดการเข้าถึงของตัวเลือกได้ โดยการตั้งค่ารูทการกำหนดขอบเขต ซึ่งจะเป็นตัวกำหนดขอบเขตบนของ Subtree ที่คุณต้องการกำหนดเป้าหมาย เมื่อตั้งค่ารูทการกำหนดขอบเขตแล้ว กฎสไตล์ที่อยู่ในรูทดังกล่าว ซึ่งเรียกว่ากฎสไตล์ที่กำหนดขอบเขต จะเลือกได้จากซับทรีที่จำกัดของ DOM เท่านั้น
เช่น หากต้องการกำหนดเป้าหมายเฉพาะองค์ประกอบ <img> ในคอมโพเนนต์ .card คุณจะต้องตั้งค่า .card เป็นรูทการกำหนดขอบเขตของกฎ @ @scope
@scope (.card) {
img {
border-color: green;
}
}
กฎรูปแบบที่กำหนดขอบเขต img { … } จะเลือกได้เฉพาะองค์ประกอบ <img> ที่อยู่ในขอบเขตขององค์ประกอบ .card ที่ตรงกันเท่านั้น
หากต้องการป้องกันไม่ให้เลือกองค์ประกอบ <img> ภายในพื้นที่เนื้อหาของการ์ด (.card__content) คุณสามารถทำให้ตัวเลือก img มีความเฉพาะเจาะจงมากขึ้นได้ อีกวิธีหนึ่งในการทำเช่นนี้คือการใช้ข้อเท็จจริงที่ว่า@scopeกฎ @ ยังยอมรับขีดจำกัดการกำหนดขอบเขตซึ่งกำหนดขอบเขตล่างด้วย
@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) { ... }
กฎสไตล์ที่กำหนดขอบเขตเองจะหลุดออกจาก Subtree ไม่ได้ การเลือกอย่าง :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 จะยังคงสืบทอดลงไป
ยังองค์ประกอบย่อยภายในรูของโดนัท
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
ในตัวอย่างนี้ องค์ประกอบ .card__content และองค์ประกอบย่อยมีสี hotpink เนื่องจากรับค่ามาจาก .card