Shadow Root คือซับทรี DOM ที่แคปซูลไว้ซึ่งแนบอยู่กับองค์ประกอบโฮสต์ โดยสไตล์ภายในจะไม่รั่วไหลออกไป และสไตล์ของหน้าเว็บจะไม่รั่วไหลเข้ามา (ยกเว้นพร็อพเพอร์ตี้ที่รับค่ามา) เทมเพลตจะสร้างแผนผังอย่างมีประสิทธิภาพ ส่วนสล็อตจะฉายเนื้อหา Light DOM เข้าไปในแผนผัง
การแนบ Shadow Root
this.attachShadow({ mode: 'open', delegatesFocus: true });
delegatesFocus: true ทำให้การโฟกัสโฮสต์ย้ายโฟกัสไปยังองค์ประกอบแรกที่โฟกัสได้ภายใน Shadow Tree, กำหนดเส้นทางสไตล์ :focus ไปยังโฮสต์ และส่งต่อการคลิก <label> ไปยังการควบคุมภายในในองค์ประกอบที่กำหนดเองที่เชื่อมโยงกับแบบฟอร์ม (static formAssociated = true) ตั้งค่าเมื่อใดก็ตามที่คอมโพเนนต์รวมการควบคุมที่โฟกัสได้ ใช้ mode: 'open' ในเกือบทุกกรณีเพื่อให้เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ สคริปต์หน้าเว็บ และการไฮเดรตเซิร์ฟเวอร์เข้าถึง element.shadowRoot ได้ (ดูการวางแนวคอมโพเนนต์เว็บ)
เทมเพลต: สร้างโครงสร้างอย่างมีประสิทธิภาพ
กำหนด <template> ครั้งเดียว (ขอบเขตโมดูลหรือช่องแบบคงที่) และโคลนต่ออินสแตนซ์ การโคลนส่วนย่อยที่แยกวิเคราะห์แล้วจะเร็วกว่าการแยกวิเคราะห์สตริง innerHTML ซ้ำในการสร้างทุกครั้ง
const template = document.createElement('template');
template.innerHTML = `
<div class="card">
<slot name="header"></slot>
<div class="body"><slot></slot></div>
<slot name="footer"></slot>
</div>
`;
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// MANDATORY: deep-clone the template content so each instance gets its own nodes.
this.shadowRoot.append(template.content.cloneNode(true));
}
}
customElements.define('my-card', MyCard);
ใช้เทมเพลตที่เรียบง่าย "div soup" ที่ซ้อนกันหลายชั้นและ Shadow Root ที่ซ้อนกันหลายระดับต่างก็เพิ่มต้นทุนการแก้ไขสไตล์และเลย์เอาต์ที่วัดได้ ซึ่งส่งผลต่อประสิทธิภาพอย่างมากในวงกว้าง เลือกโครงสร้างแบบแบนและองค์ประกอบเชิงความหมาย
สล็อต: ส่วนที่สร้างความประหลาดใจให้ผู้คน
สล็อตเริ่มต้นและสล็อตที่มีชื่อจะทำงานตามที่ระบุไว้ โดยลักษณะการทำงานที่อาจทำให้เกิดปัญหา ได้แก่
- ช่องว่างมีความสำคัญ ระบบจะกำหนดโหนดข้อความระหว่างองค์ประกอบ (รวมถึงช่องว่างล้วนๆ) ให้กับช่องเริ่มต้น ดังนั้นช่องเริ่มต้นจึงแทบจะไม่ "ว่างเปล่า" จริงๆ
- ระบบจะคงลำดับ DOM ไว้ ไม่ใช่ลำดับช่องโฆษณา โหนดที่ฉายจะคงลำดับ Light DOM เดิมไว้เพื่อวัตถุประสงค์ด้าน
:first-child/:last-childไม่ว่าจะเติมช่องใดก็ตาม กฎ::slotted(*:first-child)กำหนดเป้าหมายเป็นรายการย่อย DOM รายการแรก ไม่ใช่รายการย่อยรายการแรกของช่องนั้น ดังนั้นจึงมักไม่ตรงกับสิ่งที่คุณคาดหวัง slotchangeคือสัญญาณเวลาอัปเกรด โดยจะเริ่มทำงานเมื่อโหนดที่กำหนดมีการเปลี่ยนแปลง (รวมถึงการกำหนดครั้งแรก) และเป็นวิธีที่เชื่อถือได้ในการทราบว่าโหนดบุตรหลานของ Light DOM มาถึงแล้วหลังจากการอัปเกรด ซึ่งอาจไม่มีอยู่ในระหว่างconnectedCallback
this.shadowRoot.querySelector('slot[name="header"]')
.addEventListener('slotchange', (e) => {
const nodes = e.target.assignedElements();
this.toggleAttribute('has-header', nodes.length > 0);
});
กำลังโหลดสไตล์ชีตลงใน Shadow Root
| วิธีการ | ข้อดี | ข้อเสีย |
|---|---|---|
<style> ในเทมเพลต |
ใช้งานง่าย มีการห่อหุ้มอย่างสมบูรณ์ และทำงานได้โดยไม่ต้องใช้ JS | ทำซ้ำต่ออินสแตนซ์ ซึ่งทำให้ DOM มีขนาดใหญ่ขึ้นเมื่อปรับขนาด |
<link rel="stylesheet"> |
ไฟล์ภายนอกที่แคชได้ | คำขอเพิ่มเติม เสี่ยงต่อการเกิด Flash of Unstyled Content (FOUC) |
adoptedStyleSheets |
CSSStyleSheet ที่แชร์ 1 รายการในทุกอินสแตนซ์ ปรับขนาดได้เร็วที่สุด |
ต้องใช้ JS; สไตล์ชีตที่สร้างขึ้นไม่รองรับ @import |
สำหรับคอมโพเนนต์หนึ่งที่มีหลายอินสแตนซ์ ให้เลือกสไตล์ชีตที่สร้างขึ้นที่แชร์
const sheet = new CSSStyleSheet();
sheet.replaceSync(`:host { display: block; }`);
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// The same sheet object is reused by every instance; no per-instance parse.
this.shadowRoot.adoptedStyleSheets = [sheet];
}
}