ดูวิธีใช้ @scope เพื่อเลือกองค์ประกอบภายในแผนผังย่อยที่จำกัดของ DOM เท่านั้น
การสนับสนุนเบราว์เซอร์
- 118
- 118
- x
- x
ศิลปะที่ละเอียดอ่อนในการเขียนตัวเลือก 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
เป็นรูทที่กำหนดขอบเขตของกฎระดับ @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 Nesting
@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 */
}
}
ขีดจำกัดขอบเขตสามารถใช้คลาส Pseudo :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 */
}
:root :root { /* ❌ 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>
@ขอบเขตใน 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
จะยังคงสืบทอดไปยังเด็กที่อยู่ในรูของโดนัท
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
ในตัวอย่างด้านบน องค์ประกอบ .card__content
และองค์ประกอบย่อยมีสี hotpink
เพราะรับค่ามาจาก .card
(รูปภาพปกโดย rustam burkhanov ใน Unsplash)