Shadow DOM เป็นขอบเขตของสไตล์ โดยกฎของหน้าเว็บจะไม่เข้าถึง และกฎของคอมโพเนนต์จะไม่รั่วไหลออกไป ยกเว้นพร็อพเพอร์ตี้ที่รับค่ามา (รวมถึงคุณสมบัติที่กำหนดเอง) ข้ามเส้นอย่างตั้งใจผ่านช่องทางด้านล่าง
:host, :host(), :host-context()
/* The component's own host element, styled from inside its shadow tree. */
:host { display: block; }
/* Only when the host matches a selector (e.g. a reflected state attribute). */
:host([disabled]) { opacity: 0.5; pointer-events: none; }
/* Based on an ancestor in the Light DOM; useful for theming. */
:host-context([theme="dark"]) { background: #111; color: #eee; }
หลีกเลี่ยง :host-context() ฟีเจอร์นี้จะเข้าถึงภายนอกขอบเขตเพื่อทดสอบระดับบน แต่ Firefox และ Safari ปฏิเสธที่จะจัดส่ง และ CSSWG ก็มีแนวโน้มที่จะไม่พัฒนาฟีเจอร์นี้ต่อ โดยถือว่าเป็นทางตันสำหรับ Chromium เท่านั้น ไม่ใช่ตัวเลือกที่มองไปข้างหน้า ธีมผ่านคุณสมบัติที่กำหนดเองของ CSS ที่รับค่ามาแทน (ด้านล่าง): ผู้บริโภคตั้งค่าคุณสมบัติในระดับบนและคอมโพเนนต์จะอ่านค่าข้ามขอบเขต ซึ่งใช้ได้ทุกที่และทำให้ทั้ง 2 ฝั่งแยกกัน
พร็อพเพอร์ตี้ที่รับช่วงมาและคุณสมบัติที่กำหนดเองจะข้ามขอบเขต
พร็อพเพอร์ตี้ CSS ที่รับค่ามา (color, font-family, line-height และพร็อพเพอร์ตี้ที่กำหนดเองของ CSS) จะส่งผ่านขอบเขต Shadow ซึ่งทำให้คุณสมบัติที่กำหนดเองเป็น API การกำหนดธีมหลัก โดยแสดงคุณสมบัติเหล่านั้นด้วยทางเลือกที่เหมาะสม
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
คุณสมบัติที่กำหนดเองซึ่งกำหนดใน :host จะถูกลบล้างได้โดยกฎที่กำหนดเป้าหมายองค์ประกอบโฮสต์โดยเฉพาะเท่านั้น หากต้องการให้รับค่าและลบล้างได้จากทุกที่ในลำดับชั้น ให้ปล่อยพร็อพเพอร์ตี้สาธารณะใน :host ไว้โดยไม่ต้องตั้งค่า และใช้ค่าสำรองใน var() แต่ละรายการ อย่าสร้างพร็อพเพอร์ตี้สาธารณะใน :host
หากต้องการให้ประกาศโฆษณาสำรองเพียงครั้งเดียวแทนที่จะประกาศซ้ำในทุก var() ให้กำหนดพร็อพเพอร์ตี้ "Shadow" ส่วนตัวใน :host ที่อ่านพร็อพเพอร์ตี้สาธารณะ และอ้างอิงพร็อพเพอร์ตี้ส่วนตัวภายใน ดังนี้
:host {
--_card-bg: var(--card-bg, white);
--_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }
ผู้บริโภคยังคงลบล้าง --card-bg จากภายนอกได้ ขีดล่างนำหน้าจะส่งสัญญาณว่าชื่อแทนเป็นแบบภายใน และฟังก์ชันสำรองจะอยู่ในที่เดียวเท่านั้น
::slotted(): การจัดรูปแบบ Light DOM ที่ฉาย
::slotted(selector) กำหนดเป้าหมายโหนดระดับบนสุดที่กำหนดให้กับช่อง โดยไม่สามารถเข้าถึงองค์ประกอบสืบทอด และจะแพ้กฎ Light DOM (ผู้บริโภคเป็นเจ้าของเนื้อหาของตนเอง) ดังนั้นให้ใช้เพื่อการปรับแต่งเล็กน้อย ไม่ใช่การควบคุม ::slotted(...) !important สามารถเอาชนะกฎของผู้บริโภคได้ แต่การทำเช่นนั้นจะลบล้างการจัดรูปแบบที่ผู้บริโภคมีสิทธิ์เป็นเจ้าของ โปรดสงวนไว้สำหรับการแก้ไขโครงสร้าง ไม่ใช่การลบล้างด้านความสวยงาม
/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }
::part() และ exportparts: แสดงข้อมูลภายในสำหรับการกำหนดธีม
ทำเครื่องหมายองค์ประกอบภายในด้วย part="name" เพื่อให้ผู้บริโภคจัดรูปแบบด้วยพร็อพเพอร์ตี้ใดก็ได้ โดยไม่ต้องเรียนรู้โครงสร้างภายในของคุณ
<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }
แสดงส่วนระดับสูงบางส่วน (ปุ่ม, อินพุต, ป้ายกำกับ) ไม่ใช่ทุก <div>; พื้นผิวส่วนที่กว้างจะกลายเป็น API ที่เปราะบาง หากต้องการแสดงส่วนต่างๆ ของคอมโพเนนต์ที่ฝังไว้ขึ้นไปด้านบนผ่านคอมโพเนนต์ของคุณเอง ให้ส่งต่อส่วนเหล่านั้นด้วย exportparts ดังนี้
<inner-widget exportparts="submit: card-submit"></inner-widget>
เลือกช่องทางใดช่องทางหนึ่งอย่างรอบคอบ ได้แก่ คุณสมบัติที่กำหนดเองสำหรับชุดค่าที่จำกัดและปลอดภัยสำหรับระบบการออกแบบ หรือส่วนต่างๆ สำหรับการจัดรูปแบบองค์ประกอบที่เฉพาะเจาะจงแบบไม่จำกัด
การส่งต่อสไตล์ไปยังองค์ประกอบเนทีฟที่รวมไว้
เมื่อองค์ประกอบที่กำหนดเองรวมการควบคุมแบบเนทีฟ (<my-button> รอบ <button> จริง) มักจะมี 3 สิ่งที่ทำให้ผู้ใช้สับสน ได้แก่
- การควบคุมแบบฟอร์มจะไม่รับช่วงการจัดพิมพ์
colorและคุณสมบัติที่กำหนดเองจะข้ามขอบเขต แต่<button>,<input>และ<select>จะใช้fontและองค์ประกอบที่เกี่ยวข้องจากสไตล์ชีต UA ซึ่งไม่รับช่วงต่อ ดังนั้น ตัวควบคุมที่รวมไว้จึงแสดงผลในแบบอักษรของแพลตฟอร์ม ไม่ใช่แบบอักษรของหน้าเว็บ จนกว่าคุณจะตั้งค่าfont: inherit(และโดยปกติคือcolor: inherit) ในตัวควบคุมภายใน Shadow Tree นี่เป็นข้อผิดพลาดที่พบบ่อยที่สุดเพียงข้อเดียว - รูปแบบ
:hostไม่เข้าถึงองค์ประกอบภายใน การตั้งค่าbackground/border/paddingใน:hostจะจัดรูปแบบกล่องโฮสต์ ไม่ใช่การควบคุมภายในกล่อง ส่งต่ออย่างรอบคอบ: แสดงการควบคุมเป็น::partสำหรับการจัดรูปแบบปลายเปิด หรือส่งชุดค่าคงที่ผ่านคุณสมบัติที่กำหนดเอง — กฎ:hostจะไม่เรียงซ้อนกันในองค์ประกอบที่รวมอยู่ display: contents+all: inheritเป็นการแลกเปลี่ยนที่แท้จริง ไม่ใช่ทางลัด ปัจจุบันการยุบโฮสต์ด้วยdisplay: contentsและการรีเซ็ตการควบคุมภายในด้วยall: inheritเป็นวิธีทั่วไปเพียงวิธีเดียวในการส่งผ่านสไตล์ที่กำหนดเองไปยังองค์ประกอบเนทีฟที่รวมไว้โดยที่ผู้ใช้ไม่ต้องเรียนรู้ส่วนประกอบต่อคอมโพเนนต์หรือคุณสมบัติที่กำหนดเอง และการนำกล่องของโฮสต์ออกมักจะเป็นจุดประสงค์ของรูปแบบ (องค์ประกอบที่ชดเชยการสืบทอดในตัวที่ขาดหายไป) ข้อควรระวังที่แท้จริง 2 ข้อคือall: inheritจะล้างค่าเริ่มต้นของ UA ดังนั้นให้กำหนดขอบเขตเป็นพร็อพเพอร์ตี้ที่เฉพาะเจาะจงหรือใช้revertเมื่อต้องการให้ค่าเริ่มต้นกลับมา และตัวบล็อกที่เป็นรูปธรรมคือgetBoundingClientRect()และgetClientRects()จะแสดงผลเป็น 0 สำหรับองค์ประกอบdisplay: contentsดังนั้นการกำหนดตำแหน่ง Anchor เทียบกับโฮสต์จึงใช้งานไม่ได้ ใช้::partหรือคุณสมบัติที่กำหนดเองเมื่อข้อดีข้อเสียเหล่านั้นไม่เหมาะสม
:state(): จัดรูปแบบสถานะของคอมโพเนนต์เอง
องค์ประกอบที่กำหนดเองสามารถแสดงสถานะบูลีนภายในผ่าน ElementInternals ผ่าน this.#internals.states.add('busy') และ .delete('busy') (ดูองค์ประกอบที่กำหนดเองสำหรับการใช้งานวงจร) และสถานะเหล่านั้นจะกลายเป็นช่องทางการจัดรูปแบบข้ามขอบเขต เช่น ::part และคุณสมบัติที่กำหนดเอง แต่สำหรับสถานะแทนที่จะเป็นโครงสร้างหรือค่า จับคู่องค์ประกอบเหล่านั้นภายใน Shadow Tree ด้วย :host(:state(name)) และจากสไตล์ชีตของผู้ใช้ด้วย tag-name:state(name) ดังนี้
/* Inside the component's shadow tree. */
:host(:state(busy)) button { opacity: 0.6; pointer-events: none; }
/* From the consumer's stylesheet — a read-only public hook. */
favorite-button:state(favorited) { filter: drop-shadow(0 0 4px crimson); }
สถานะที่กำหนดเองจะอ่านอย่างเดียวจากภายนอก ซึ่งต่างจากแอตทริบิวต์ที่แสดงผล กล่าวคือ ผู้ใช้สามารถจัดรูปแบบได้ แต่จะตั้งค่าหรือปลอมแปลงไม่ได้ ดังนั้นคอมโพเนนต์จึงยังคงเป็นผู้เขียนเพียงรายเดียว ใช้แอตทริบิวต์นี้แทนแอตทริบิวต์ data-* เมื่อคอมโพเนนต์เป็นเจ้าของสถานะและต้องจัดรูปแบบเท่านั้น
@layer และการค้นหาคอนเทนเนอร์ภายในคอมโพเนนต์
- เลเยอร์แบบเรียงซ้อน (
@layer) ทำงานภายใน Shadow Root และกำหนดขอบเขตไว้ที่ Shadow Root ชื่อเลเยอร์ใน Shadow Tree จะเป็นอิสระจากเลเยอร์ที่มีชื่อเดียวกันในหน้าเว็บ ใช้เพื่อจัดลำดับสไตล์คอมโพเนนต์ของคุณเอง (เช่น@layer base, theme;) เพื่อให้การลบล้างของผู้บริโภคผ่านคุณสมบัติที่กำหนดเองและ::partยังคงชนะตามที่คาดการณ์ไว้ - การค้นหาคอนเทนเนอร์เป็นเครื่องมือที่เหมาะสำหรับคอมโพเนนต์ที่ปรับเปลี่ยนตามอุปกรณ์ โดยคอมโพเนนต์ควรตอบสนองต่อพื้นที่ที่ได้รับ ไม่ใช่พอร์ตการมองเห็น ประกาศโฮสต์หรือ Wrapper เป็นคอนเทนเนอร์ (ให้
:hostเป็นกล่องเลย์เอาต์ เช่นdisplay: blockเนื่องจากองค์ประกอบที่กำหนดเองมีค่าเริ่มต้นเป็นdisplay: inlineและการจำกัดขนาดใช้ไม่ได้กับองค์ประกอบinlineหรือcontents) และค้นหาองค์ประกอบดังกล่าว เพื่อให้คอมโพเนนต์เดียวกันปรับเปลี่ยนในแถบด้านข้างหรือพื้นที่แบบเต็มความกว้าง
:host {
display: block; /* Required: size containment does not apply to default display: inline */
container-type: inline-size;
}
@container (min-width: 30rem) {
.card { grid-template-columns: 1fr 2fr; }
}
กลยุทธ์สำรอง
::part, เลเยอร์ Cascade และการค้นหาคอนเทนเนอร์พร้อมใช้งานอย่างกว้างขวาง ธีมส่วนใหญ่จะใช้พร็อพเพอร์ตี้ที่กำหนดเองที่รับค่ามา ซึ่งจะทำงานได้แม้ว่าสิ่งเหล่านี้จะไม่ทำงานก็ตาม ตามที่ระบุไว้ข้างต้น อย่าใช้ :host-context() เนื่องจากใช้ได้กับ Chromium เท่านั้นและไม่ใช่ตัวเลือกที่เหมาะสำหรับอนาคต