ดูวิธีใช้ @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
เพื่อจำกัดการเข้าถึงของตัวเลือกได้ โดยการตั้งค่ารูทที่กำหนดขอบเขตซึ่งกำหนดขอบเขตด้านบนของแผนผังย่อยที่ต้องการกำหนดเป้าหมาย เมื่อมีชุดรูทที่กำหนดขอบเขต กฎสไตล์ที่มีอยู่ซึ่งมีชื่อว่ากฎรูปแบบที่กำหนดขอบเขต จะเลือกได้จากแผนผังย่อยแบบจำกัดของ DOM เท่านั้น
ตัวอย่างเช่น หากต้องการกำหนดเป้าหมายเฉพาะองค์ประกอบ <img>
ในคอมโพเนนต์ .card
คุณต้องกำหนด .card
เป็นรากที่กำหนดขอบเขตของกฎ at @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 Nest ก็ได้
@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>
เมื่อดูมาร์กอัปส่วนเล็กๆ ดังกล่าว ลิงก์ที่สามจะเป็น white
แทนที่จะเป็น black
แม้ว่าจะเป็นลูกของ div
ที่มีการใช้คลาส .light
ก็ตาม เนื่องจากลำดับเกณฑ์การปรากฏที่ Cascade ใช้ที่นี่เพื่อกำหนดผู้ชนะ พบว่า .dark a
ได้รับการประกาศเป็นลำดับสุดท้าย ดังนั้นจึงจะชนะจากกฎ .light a
คุณจะสามารถแก้ปัญหานี้ได้ด้วยเกณฑ์ระยะใกล้ที่กำหนดขอบเขต
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
เนื่องจากตัวเลือก a
ที่กำหนดขอบเขตทั้ง 2 รายการมีความจำเพาะเท่ากัน เกณฑ์ระยะใกล้ที่กำหนดขอบเขตจะเริ่มทำงาน โดยจะให้น้ำหนักของตัวเลือกทั้งสองตามระยะห่างจากรากที่กำหนดขอบเขต สำหรับองค์ประกอบ a
รายการที่ 3 นั้น จะเป็นการฮ็อปครั้งเดียวไปยังรูทที่กำหนดขอบเขต .light
แต่ 2 รายการไปยังรูท .dark
เท่านั้น ดังนั้นตัวเลือก a
ใน .light
จะเป็นผู้ชนะ
หมายเหตุสรุป: การแยกตัวเลือก ไม่ใช่การแยกรูปแบบ
สิ่งสำคัญที่ควรทราบอย่างหนึ่งคือ @scope
จะจำกัดการเข้าถึงของตัวเลือก โดยไม่มีการแยกรูปแบบ พร็อพเพอร์ตี้ที่รับช่วงต่อไปยังระดับย่อยจะยังคงรับค่าจากขอบเขตล่างของ @scope
พร็อพเพอร์ตี้หนึ่งคือพร็อพเพอร์ตี้ color
เมื่อประกาศว่าโดนัทในขอบเขตโดนัท color
จะยังคงสืบทอดต่อกันเป็นชั้นๆ ภายในรูของโดนัท
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
ในตัวอย่างข้างต้น องค์ประกอบ .card__content
และองค์ประกอบย่อยมีสี hotpink
เนื่องจากรับค่าจาก .card
(ภาพหน้าปกโดย rustam burkhanov บน Unsplash)