Host Element และ Shadow Boundary
Host element คืออะไร
หัวข้อที่มีชื่อว่า “Host element คืออะไร”Host element คือ element ที่เป็นเจ้าของ shadow root — เช่น <my-badge> หรือ <host-badge> จากภายใน shadow tree คุณสามารถกำหนดเป้าหมายและสไตล์ host element ได้ด้วย pseudo-class selector :host
เรื่องนี้สำคัญเพราะ host element เองอยู่ใน light DOM ตามปกติ shadow CSS จะเข้าไม่ถึง light DOM เลย — แต่ :host คือข้อยกเว้นพิเศษที่ให้ shadow tree สไตล์ container ของตัวเองได้
Selector :host
หัวข้อที่มีชื่อว่า “Selector :host”:host กำหนดเป้าหมายไปที่ host element จากภายใน shadow root:
/* inside shadow root <style> */:host { display: inline-flex; padding: 0.25rem 0.75rem; border-radius: 9999px; border: 2px solid #6366f1; background: #eef2ff;}ถ้าไม่มี :host { display: ... } custom element จะใช้ค่าเริ่มต้น display: inline ซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ
Functional selector :host()
หัวข้อที่มีชื่อว่า “Functional selector :host()”:host(selector) ให้คุณสไตล์ host แบบมีเงื่อนไข ตาม attribute, class หรือ state ของมันเอง:
/* style the host when it has a [disabled] attribute */:host([disabled]) { opacity: 0.5; cursor: not-allowed; pointer-events: none;}
/* style the host when it has a specific class */:host(.primary) { background: #4f46e5; color: white;}pattern นี้พบได้บ่อยมาก — สถานะของ component (active, disabled, highlighted) มักถูกสื่อสารผ่าน attribute และ :host([attr]) คือวิธีที่ถูกต้องในการตอบสนองต่อมันจากภายใน shadow CSS
Shadow boundary สำหรับ selector
หัวข้อที่มีชื่อว่า “Shadow boundary สำหรับ selector”Shadow boundary เข้มงวดกับ CSS selector ทั้งสองทิศทาง:
/* page CSS — CANNOT reach inside <my-badge> shadow root */my-badge p { color: red; } /* no effect on shadow tree */
/* shadow CSS — CANNOT reach outside into the light DOM */p { color: blue; } /* only affects <p> inside this shadow tree */ข้อยกเว้นมีเพียง CSS custom properties (ตัวแปร) และ pseudo-element ::part() ซึ่งถูกออกแบบมาให้ข้ามขอบเขตได้โดยเฉพาะ
Event และ Retargeting
หัวข้อที่มีชื่อว่า “Event และ Retargeting”Event ส่วนใหญ่ bubble ผ่าน shadow boundary ได้ แต่เมื่อทำเช่นนั้น browser จะทำ retarget — event.target ที่ listener ใน light DOM เห็นจะเป็น host element ไม่ใช่ element จริงภายใน shadow tree ที่ยิง event นั้นออกมา
document.querySelector('my-badge').addEventListener('click', function(e) { console.log(e.target); // <my-badge> — not the <button> inside the shadow});สิ่งนี้รักษา encapsulation ไว้: โค้ดใน light DOM มองไม่เห็นโครงสร้างภายในของ component ผ่าน event target
Light DOM เทียบกับ Shadow DOM
หัวข้อที่มีชื่อว่า “Light DOM เทียบกับ Shadow DOM”| Light DOM | Shadow DOM | |
|---|---|---|
| นิยาม | children ที่คุณวางไว้ใน element จากภายนอก | shadow tree ส่วนตัวที่ element แนบไว้เอง |
| การมองเห็น | อยู่ใน document tree ปกติ | ถูก encapsulate เข้าถึงผ่าน shadow root |
| การสไตล์ | สไตล์ด้วย page CSS | สไตล์ด้วย shadow CSS |
| Slots | ถูก project เข้า shadow ผ่าน <slot> | เป็นที่อยู่ของ <slot> element เอง |
ตัวอย่างที่รันได้
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้”ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Event retargeting ที่ shadow boundary | โค้ดภายนอกไม่เห็น internal structure ของ component ผ่าน event.target | debug event ยากขึ้น เพราะ event.target ที่ host ไม่ใช่ element จริงที่ถูกคลิกภายใน shadow tree |
สไตล์ host ผ่าน :host / :host() จากภายใน shadow CSS | component ควบคุม box model ของตัวเองได้ (display, border) โดยไม่ต้องพึ่งสไตล์ภายนอก | โค้ดภายนอกยังคง override บาง property จาก host element ได้ (เช่น inline style) ทำให้ผลลัพธ์ขึ้นกับใครเขียนทีหลัง |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- คาดหวังว่า
event.targetในหน้าเว็บจะชี้ไปที่ element จริงภายใน shadow tree — เพราะ event retargeting จะเปลี่ยนevent.targetให้เป็น host element เสมอเมื่อ event ฟองออกมาถึง light DOM - ใช้ selector ธรรมดาแทน
:hostเพื่อสไตล์ตัว host element เอง — เขียนhost-badge { border: ... }ใน shadow CSS แล้วสงสัยว่าทำไมไม่ทำงาน ต้องใช้:hostเท่านั้นเพราะ host element อยู่นอก shadow tree - สับสนระหว่าง light DOM กับ shadow DOM — คิดว่าเนื้อหาที่ project ผ่าน
<slot>อยู่ใน shadow tree ทั้งที่จริงๆ light DOM content ยังคงเป็นของ caller และแก้ไขสไตล์จาก shadow root ไม่ได้โดยตรง
💡 ตัวอย่างจากของจริง
Salesforce Lightning (LWC) — ใช้
:hoststyling อย่างเป็นระบบเพื่อให้ทุก component ควบคุม layout ของตัวเองได้โดยไม่ต้องพึ่ง global CSS ของหน้า (salesforce/lwc)