ข้ามไปยังเนื้อหา

การแนบ Shadow Root

Shadow tree ไม่ได้เกิดขึ้นเองเมื่อ custom element ถูกสร้าง — คุณต้องขอ shadow tree อย่างชัดเจนก่อน ถึงจะได้ DOM ที่แยกขอบเขตออกจากหน้าเว็บภายนอก

ในการสร้าง shadow tree ให้เรียก this.attachShadow() บน host element และส่ง mode option เข้าไป method นี้จะคืนค่า shadow root ใหม่และเก็บไว้เป็น this.shadowRoot ด้วย

class ScopedCard extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
}
}

คุณสามารถเรียก attachShadow ได้ใน constructor หรือใน connectedCallback การใช้ constructor เหมาะเมื่อต้องการให้ shadow root พร้อมใช้งานทันที ส่วน connectedCallback ก็ใช้ได้ดีเมื่อต้องการเข้าถึง attribute ที่กำหนดใน HTML ด้วย

หลังจากเรียก attachShadow แล้ว shadow root จะเข้าถึงได้ 2 วิธี:

// คืนค่าโดยตรงจากการเรียก
var root = this.attachShadow({ mode: 'open' });
// เก็บไว้เป็น property บน element ด้วย
var root = this.shadowRoot;

ทั้งสอง reference ชี้ไปยัง ShadowRoot object เดียวกัน ShadowRoot ทำงานเหมือน DocumentFragment — คุณสามารถเรียก querySelector, getElementById และกำหนด innerHTML บน object นี้ได้

วิธีที่ง่ายที่สุดในการเติมเนื้อหาใน shadow tree คือการกำหนด this.shadowRoot.innerHTML วาง <style> tag ไว้ก่อน ตามด้วย markup:

this.shadowRoot.innerHTML =
'<style>' +
' .card { border: 2px solid #6366f1; border-radius: 8px; padding: 1rem; background: #eef2ff; }' +
' h2 { color: #4f46e5; margin: 0 0 0.5rem; }' +
'</style>' +
'<div class="card"><h2>Shadow Card</h2><p>Styles scoped inside shadow root</p></div>';

สไตล์จะใช้ได้เฉพาะภายใน shadow tree เท่านั้น element .card ที่อยู่ใน light DOM จะไม่ได้รับผลกระทบ

class ScopedCard extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML =
'<style>' +
' .card { border: 2px solid #6366f1; border-radius: 8px; padding: 1rem; background: #eef2ff; }' +
' h2 { color: #4f46e5; margin: 0 0 0.5rem; }' +
' p { color: #3730a3; margin: 0; }' +
'</style>' +
'<div class="card"><h2>Shadow Card</h2><p>Styles scoped inside shadow root</p></div>';
}
}
customElements.define('scoped-card', ScopedCard);
ตัวเลือกBenefitCost
เรียก attachShadow ใน constructorshadow root พร้อมใช้งานทันทีตั้งแต่สร้าง instanceยังอ่าน attribute จาก HTML ไม่ได้ในจังหวะนี้
เรียก attachShadow ใน connectedCallbackอ่าน attribute ที่ประกาศใน HTML ได้ตั้งแต่ครั้งแรกที่ element เข้า DOMต้องเช็คก่อนว่ามี shadow root อยู่แล้วหรือยัง เผื่อ element ถูก reconnect
  • เรียก attachShadow ซ้ำ — ถ้า element ถูกย้ายตำแหน่งใน DOM (disconnectedCallback ตามด้วย connectedCallback อีกครั้ง) และโค้ดเรียก attachShadow ใหม่ทุกครั้ง จะได้ error เพราะ shadow root แนบได้แค่ครั้งเดียวต่อ element ควรเช็ค this.shadowRoot ก่อนเรียกซ้ำ
  • ตั้งค่า innerHTML ก่อนเรียก attachShadowthis.shadowRoot จะยังเป็น null จนกว่าจะเรียก attachShadow แล้ว ต้องเรียกก่อนเสมอ
  • ต่อ string HTML ด้วยมือจาก user input — การ concatenate innerHTML แบบในตัวอย่างสะดวกสำหรับ demo แต่ถ้าข้อมูลมาจากภายนอกควรใช้ textContent หรือ sanitize ก่อน ไม่งั้นเสี่ยง XSS

💡 ตัวอย่างจากของจริง

YouTube — ใช้ attachShadow ในการสร้าง video player component (<yt-icon-button> และองค์ประกอบอื่นๆ) เพื่อแยกโครงสร้างภายในออกจากหน้าเว็บหลัก

Salesforce Lightning (LWC) — เรียก attachShadow เป็นส่วนหนึ่งของ lifecycle ของทุก component เพื่อ encapsulate UI ระดับ enterprise ที่มีหลายทีมพัฒนาพร้อมกัน

`this.attachShadow({ mode: "open" })` คืนค่าอะไร?
หลังจากเรียก `this.attachShadow({ mode: "open" })` แล้ว จะเข้าถึง shadow root ในภายหลังได้อย่างไร?
ต้องวาง `<style>` tag ที่ไหนเพื่อให้สไตล์มีขอบเขตเฉพาะใน shadow tree?