Shadow DOM v1 - คอมโพเนนต์เว็บในตัว

Shadow DOM ช่วยให้นักพัฒนาเว็บสร้าง DOM และ CSS ที่มีการจัดแบ่งสำหรับคอมโพเนนต์เว็บได้

สรุป

Shadow DOM กำจัดความยุ่งยากในการสร้างเว็บแอป ความบอบบางนี้มาจากธรรมชาติที่เป็นสากลของ HTML, CSS และ JS หลายปีที่ผ่านมา เราได้คิดค้นจำนวนtoolsจำนวนมากเพื่อหลีกเลี่ยงปัญหาดังกล่าว เช่น เมื่อคุณใช้รหัส/คลาส HTML ใหม่ ก็จะไม่มีการบอกว่ารหัสนั้นจะขัดแย้งกับชื่อเดิมที่หน้าเว็บใช้หรือไม่ ข้อบกพร่องเล็กๆ น้อยๆ ค่อยๆ เล็ดลอดเข้ามา ความเจาะจงของ CSS จะกลายเป็นปัญหาใหญ่ (!important ถูกทุกข้อ!) ตัวเลือกสไตล์ควบคุมไม่ได้ และประสิทธิภาพอาจแย่ลง รายการจะดำเนินต่อไป

Shadow DOM แก้ไข CSS และ DOM ซึ่งเป็นการแนะนำสไตล์ที่กำหนดขอบเขต ในแพลตฟอร์มเว็บ หากไม่มีเครื่องมือหรือแบบแผนการตั้งชื่อ คุณจะรวม CSS ที่มีมาร์กอัป ซ่อนรายละเอียดการใช้งาน และเขียนคอมโพเนนต์ที่กำหนดเองในวานิลลา JavaScript ได้

เกริ่นนำ

Shadow DOM เป็นหนึ่งในมาตรฐานคอมโพเนนต์ของเว็บ 3 รายการ ได้แก่ เทมเพลต HTML, Shadow DOM และองค์ประกอบที่กำหนดเอง การนำเข้า HTML เคยเป็นส่วนหนึ่งของรายการ แต่ตอนนี้จะถือว่าเลิกใช้งานแล้ว

คุณไม่จำเป็นต้องเขียนคอมโพเนนต์เว็บที่ใช้ Shadow DOM แต่เมื่อทำแล้ว คุณจะได้ใช้ประโยชน์จาก (การจำกัด CSS, การห่อหุ้ม DOM, การเรียบเรียง) และสร้างองค์ประกอบที่กำหนดเองซึ่งนำมาใช้งานใหม่ได้ ซึ่งมีความยืดหยุ่น กำหนดค่าได้สูง และนำมาใช้ซ้ำได้มาก หากองค์ประกอบที่กำหนดเองเป็นวิธีในการสร้าง HTML ใหม่ (ด้วย JS API) Shadow DOM จะเป็นการระบุ HTML และ CSS API ทั้งสองนี้รวมกันเป็นคอมโพเนนต์ ที่มี HTML, CSS และ JavaScript ในตัว

Shadow DOM ได้รับการออกแบบมาเพื่อเป็นเครื่องมือในการสร้างแอปแบบคอมโพเนนต์ ด้วยเหตุนี้จึงนำเสนอวิธีแก้ปัญหาที่พบได้ทั่วไปในการพัฒนาเว็บ

  • Isolated DOM: DOM ของคอมโพเนนต์จะคงอยู่ในตัว (เช่น document.querySelector() จะไม่แสดงโหนดใน Shadow DOM ของคอมโพเนนต์)
  • CSS ที่กำหนดขอบเขต: CSS ที่กำหนดภายใน Shadow DOM จะกำหนดขอบเขตเอาไว้ กฎของรูปแบบ ต้องไม่รั่วและสไตล์หน้าเว็บไม่มีขีดจำกัด
  • การเรียบเรียง: ออกแบบ API เชิงประกาศที่ใช้มาร์กอัปสำหรับคอมโพเนนต์ของคุณ
  • ลดความซับซ้อนของ CSS - DOM ที่กำหนดขอบเขตหมายความว่าคุณสามารถใช้ตัวเลือก CSS แบบง่าย ชื่อรหัส/คลาสที่เป็นแบบทั่วไปมากขึ้น และไม่ต้องกังวลว่าการตั้งชื่อขัดแย้งกัน
  • ประสิทธิภาพการทำงาน - ลองนึกถึงแอปเป็นกลุ่มของ DOM แทนที่จะเป็นหน้าขนาดใหญ่ (ส่วนกลาง)

การสาธิต fancy-tabs

ตลอดทั้งบทความนี้ เราจะพูดถึงคอมโพเนนต์เดโม (<fancy-tabs>) และอ้างอิงข้อมูลโค้ดจากคอมโพเนนต์ดังกล่าว หากเบราว์เซอร์รองรับ API คุณจะเห็นการสาธิตสดที่ด้านล่าง หรือลองดูแหล่งข้อมูลแบบสมบูรณ์ใน GitHub

ดูซอร์สบน GitHub

Shadow DOM คืออะไร

พื้นหลังเกี่ยวกับ DOM

HTML ช่วยเพิ่มประสิทธิภาพให้เว็บเนื่องจากทำงานง่าย การประกาศแท็ก 2-3 รายการช่วยให้คุณสามารถเขียนหน้าเว็บ ซึ่งมีทั้งการนำเสนอและโครงสร้างได้ในไม่กี่วินาที แต่ HTML เองก็ไม่ได้มีประโยชน์แค่นั้น มนุษย์จะเข้าใจภาษาแบบข้อความได้อย่างง่ายดาย แต่แมชชีนต้องการอะไรที่มากกว่านี้ ป้อน Document Object Model หรือ DOM

เมื่อเบราว์เซอร์โหลดหน้าเว็บ เบราว์เซอร์จะแสดงสิ่งที่น่าสนใจมากมาย สิ่งหนึ่งที่ทำได้คือการเปลี่ยน HTML ของผู้เขียนให้เป็นเอกสารที่เผยแพร่อยู่ โดยพื้นฐานแล้ว เพื่อให้เข้าใจโครงสร้างของหน้าเว็บ เบราว์เซอร์จะแยกวิเคราะห์ HTML (สตริงข้อความแบบคงที่) เป็นโมเดลข้อมูล (วัตถุ/โหนด) เบราว์เซอร์จะรักษาลำดับชั้นของ HTML ไว้ด้วยการสร้างโครงสร้างของโหนดเหล่านี้ ได้แก่ DOM ข้อดีของ DOM คือการนำเสนอหน้าเว็บแบบเรียลไทม์ โหนดที่สร้างโดยเบราว์เซอร์นี้มีคุณสมบัติ วิธีการ และสิ่งที่ดีที่สุดคือ... สามารถปรับแต่งโดยโปรแกรมต่างๆ ซึ่งแตกต่างจาก HTML แบบคงที่ที่เราเขียนขึ้น เราจึงสร้างองค์ประกอบ DOM ได้โดยตรงโดยใช้ JavaScript

const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello DOM';
header.appendChild(h1);
document.body.appendChild(header);

สร้างมาร์กอัป HTML ต่อไปนี้

<body>
    <header>
    <h1>Hello DOM</h1>
    </header>
</body>

สบายดี แล้วShadow DOM คืออะไร

DOM... ในเงา

Shadow DOM เป็น DOM ปกติที่มีความแตกต่าง 2 อย่าง คือ 1) สร้าง/ใช้งานอย่างไร และ 2) ลักษณะการทำงานเมื่อเทียบกับส่วนอื่นของหน้าเว็บ โดยปกติแล้ว คุณจะสร้างโหนด DOM และต่อท้ายเป็นโหนดย่อยขององค์ประกอบอื่น การใช้ Shadow DOM เป็นการสร้างแผนผัง DOM ที่มีขอบเขตซึ่งแนบอยู่กับองค์ประกอบ แต่แยกออกจากองค์ประกอบย่อยจริงๆ ขององค์ประกอบนั้น แผนผังย่อยที่กำหนดขอบเขตนี้เรียกว่าเงาต้นไม้ องค์ประกอบที่แนบอยู่คือโฮสต์เงา ทุกอย่างที่คุณเพิ่มในเงาจะกลายเป็นภายในองค์ประกอบโฮสติ้ง รวมถึง <style> นี่คือวิธีที่ Shadow DOM มีประสิทธิภาพในการกำหนดขอบเขตสไตล์ CSS

กำลังสร้าง Shadow DOM

Shadow Root คือส่วนย่อยเอกสารที่แนบกับองค์ประกอบ "โฮสต์" การแนบ Shadow Root คือการที่องค์ประกอบได้รับ Shadow DOM หากต้องการสร้าง Shadow DOM สำหรับองค์ประกอบ ให้เรียก element.attachShadow():

const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().

// header.shadowRoot === shadowRoot
// shadowRoot.host === header

ฉันใช้ .innerHTML เพื่อเติม Shadowรูท แต่คุณก็ใช้ DOM API อื่นๆ ได้เช่นกัน นี่คือเว็บ เรามีทางเลือก

ข้อกำหนดกำหนดรายการองค์ประกอบที่ไม่สามารถโฮสต์แสงเงาได้ มีหลายสาเหตุที่องค์ประกอบ อาจอยู่ในรายการดังกล่าว

  • เบราว์เซอร์โฮสต์ Shadow DOM ภายในของตัวเองสำหรับองค์ประกอบนั้นแล้ว (<textarea>, <input>)
  • องค์ประกอบไม่เหมาะที่จะโฮสต์ Shadow DOM (<img>)

ตัวอย่างเช่น วิธีนี้ไม่ได้ผล

    document.createElement('input').attachShadow({mode: 'open'});
    // Error. `<input>` cannot host shadow dom.

การสร้าง Shadow DOM สำหรับองค์ประกอบที่กำหนดเอง

Shadow DOM มีประโยชน์อย่างยิ่งเมื่อสร้างองค์ประกอบที่กำหนดเอง ใช้ Shadow DOM ในการจัดระดับ HTML, CSS และ JS ขององค์ประกอบเพื่อสร้าง "คอมโพเนนต์เว็บ"

ตัวอย่าง - องค์ประกอบที่กำหนดเองแนบ Shadow DOM เข้ากับตัวเอง โดยห่อหุ้ม DOM/CSS ของตัวเอง

// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to <fancy-tabs>.
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
        <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
        <div id="tabs">...</div>
        <div id="panels">...</div>
    `;
    }
    ...
});

มีสิ่งที่น่าสนใจ 2-3 สิ่งที่เกิดขึ้นที่นี่ อย่างแรกคือองค์ประกอบที่กำหนดเองจะสร้าง Shadow DOM ของตัวเองเมื่อสร้างอินสแตนซ์ของ <fancy-tabs> ทำใน constructor() ได้เลย ประการที่ 2 เนื่องจากเรากำลังสร้าง Shadow Root กฎ CSS ภายใน <style> จะกำหนดขอบเขตเป็น <fancy-tabs>

การจัดวางองค์ประกอบและช่องโฆษณา

การจัดวางองค์ประกอบเป็นหนึ่งในฟีเจอร์ที่ไม่ค่อยมีคนรู้จักของ Shadow DOM แต่ก็อาจเป็นฟีเจอร์ที่สำคัญที่สุด

ในวงการการพัฒนาเว็บของเรา การจัดองค์ประกอบคือวิธีที่เราสร้างแอปที่มาจาก HTML อย่างชัดเจน องค์ประกอบที่ใช้สร้างสรรค์ที่แตกต่างกัน (<div>, <header>, <form>, <input>) จะรวมกันเพื่อสร้างแอป แท็กเหล่านี้บางแท็ก ยังทำงานร่วมกันอีกด้วย การจัดองค์ประกอบคือเหตุผลที่องค์ประกอบเนทีฟ เช่น <select>, <details>, <form> และ <video> จึงมีความยืดหยุ่นมาก แต่ละแท็กยอมรับ HTML บางรายการเป็นระดับย่อยและทำสิ่งพิเศษร่วมกับแท็ก ตัวอย่างเช่น <select> รู้วิธีแสดงผล <option> และ <optgroup> เป็นวิดเจ็ตแบบเลื่อนลงและแบบเลือกหลายรายการ องค์ประกอบ <details> แสดงผล <summary> เป็นลูกศรที่ขยายได้ แม้แต่ <video> ก็ยังรู้วิธีจัดการกับเด็กบางคน แม้ว่าองค์ประกอบ <source> จะไม่แสดงผล แต่จะส่งผลต่อลักษณะการทำงานของวิดีโอ วิเศษจริงๆ

คำศัพท์: Light DOM กับ Shadow DOM

องค์ประกอบ Shadow DOM ทำให้เกิดปัจจัยพื้นฐานใหม่ๆ มากมายในการพัฒนาเว็บ ก่อนจะพูดถึงเรื่องวัชพืช เรามาปรับคำศัพท์กัน เป็นมาตรฐานเดียวกันเพื่อให้เราพูดภาษาเดียวกัน

Light DOM

มาร์กอัปที่ผู้ใช้ในคอมโพเนนต์เขียน DOM นี้อยู่นอก DOM เงาของคอมโพเนนต์ ค่านี้คือองค์ประกอบย่อยจริงๆ ขององค์ประกอบ

<better-button>
    <!-- the image and span are better-button's light DOM -->
    <img src="gear.svg" slot="icon">
    <span>Settings</span>
</better-button>

Shadow DOM

DOM ที่ผู้เขียนคอมโพเนนต์เขียน Shadow DOM จะอยู่ในคอมโพเนนต์นั้นๆ และกำหนดโครงสร้างภายใน, CSS ที่มีขอบเขต และสรุปรายละเอียดการใช้งานของคุณ นอกจากนี้ยังกำหนดวิธีแสดงผลมาร์กอัปที่เขียนโดยผู้บริโภคในคอมโพเนนต์ของคุณได้ด้วย

#shadow-root
    <style>...</style>
    <slot name="icon"></slot>
    <span id="wrapper">
    <slot>Button</slot>
    </span>

แผนผัง DOM ที่แยกเป็นหลายรายการ

ผลลัพธ์ที่เบราว์เซอร์กระจาย Light DOM ของผู้ใช้ลงใน Shadow DOM ซึ่งแสดงผลผลิตภัณฑ์ขั้นสุดท้าย แผนผังที่แยกเป็นแผนผังคือสิ่งที่คุณเห็นใน เครื่องมือสำหรับนักพัฒนาเว็บและสิ่งที่แสดงผลในหน้า

<better-button>
    #shadow-root
    <style>...</style>
    <slot name="icon">
        <img src="gear.svg" slot="icon">
    </slot>
    <span id="wrapper">
        <slot>
        <span>Settings</span>
        </slot>
    </span>
</better-button>

องค์ประกอบ <slot>

Shadow DOM เขียนแผนผัง DOM ต่างๆ ร่วมกันโดยใช้องค์ประกอบ <slot> สล็อตคือตัวยึดตำแหน่งในคอมโพเนนต์ที่ผู้ใช้สามารถเติมด้วยมาร์กอัปของตนเอง การกำหนดสล็อตอย่างน้อย 1 ช่องจะเป็นการเชิญมาร์กอัปจากภายนอกให้แสดงผลใน Shadow DOM ของคอมโพเนนต์ โดยพื้นฐานแล้ว คุณจะพูดว่า "แสดงมาร์กอัปของผู้ใช้ที่นี่"

อนุญาตให้องค์ประกอบ "ข้าม" ขอบเขต DOM ของเงาเมื่อ <slot> เชิญองค์ประกอบเหล่านั้นเข้ามา องค์ประกอบเหล่านี้เรียกว่าโหนดที่กระจาย ตามหลักแล้ว โหนดที่กระจายอาจดูแปลกประหลาด สล็อตไม่ได้ย้าย DOM ด้วยตนเอง แต่จะแสดงผลที่ตำแหน่งอื่นภายใน Shadow DOM

คอมโพเนนต์กำหนดช่องได้ตั้งแต่ 0 ช่องขึ้นไปใน Shadow DOM ช่องโฆษณาอาจว่างเปล่า หรือแสดงเนื้อหาสำรอง หากผู้ใช้ไม่ได้ให้เนื้อหา light DOM ช่องจะแสดงผลเนื้อหาสำรอง

<!-- Default slot. If there's more than one default slot, the first is used. -->
<slot></slot>

<slot>fallback content</slot> <!-- default slot with fallback content -->

<slot> <!-- default slot entire DOM tree as fallback -->
    <h2>Title</h2>
    <summary>Description text</summary>
</slot>

คุณยังสามารถสร้างช่องที่มีชื่อได้ด้วย ช่องโฆษณาที่มีชื่อคือช่องที่เจาะจงใน Shadow DOM ที่ผู้ใช้อ้างอิงตามชื่อ

ตัวอย่าง - ช่องใน Shadow DOM ของ <fancy-tabs>:

#shadow-root
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot> <!-- named slot -->
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>

ผู้ใช้คอมโพเนนต์ประกาศ <fancy-tabs> ดังนี้

<fancy-tabs>
    <button slot="title">Title</button>
    <button slot="title" selected>Title 2</button>
    <button slot="title">Title 3</button>
    <section>content panel 1</section>
    <section>content panel 2</section>
    <section>content panel 3</section>
</fancy-tabs>

<!-- Using <h2>'s and changing the ordering would also work! -->
<fancy-tabs>
    <h2 slot="title">Title</h2>
    <section>content panel 1</section>
    <h2 slot="title" selected>Title 2</h2>
    <section>content panel 2</section>
    <h2 slot="title">Title 3</h2>
    <section>content panel 3</section>
</fancy-tabs>

และหากคุณสงสัย ต้นไม้ที่ถูกแบนราบมีลักษณะประมาณนี้

<fancy-tabs>
    #shadow-root
    <div id="tabs">
        <slot id="tabsSlot" name="title">
        <button slot="title">Title</button>
        <button slot="title" selected>Title 2</button>
        <button slot="title">Title 3</button>
        </slot>
    </div>
    <div id="panels">
        <slot id="panelsSlot">
        <section>content panel 1</section>
        <section>content panel 2</section>
        <section>content panel 3</section>
        </slot>
    </div>
</fancy-tabs>

จะเห็นว่าคอมโพเนนต์ของเราจัดการการกำหนดค่าได้หลากหลาย แต่โครงสร้าง DOM ที่แบนนั้นยังคงเหมือนเดิม เรายังเปลี่ยนจาก <button> เป็น <h2> ได้ด้วย คอมโพเนนต์นี้สร้างขึ้นเพื่อจัดการกับเด็กประเภทต่างๆ... เช่นเดียวกับที่ <select> ทำ

การจัดรูปแบบ

มีตัวเลือกมากมายสำหรับการจัดรูปแบบคอมโพเนนต์เว็บ คอมโพเนนต์ที่ใช้เงา DOM สามารถจัดรูปแบบโดยหน้าหลัก กำหนดรูปแบบของตนเอง หรือมีฮุก (ในรูปแบบของคุณสมบัติที่กำหนดเองของ CSS) เพื่อให้ผู้ใช้ลบล้างค่าเริ่มต้น

รูปแบบที่คอมโพเนนต์กำหนด

การอธิบายถึงฟีเจอร์ที่มีประโยชน์ที่สุดของ Shadow DOM คือ CSS ที่มีขอบเขต ดังนี้

  • ตัวเลือก CSS จากหน้าด้านนอกจะไม่ใช้ในคอมโพเนนต์
  • รูปแบบที่กําหนดไว้ด้านในจะไม่มีการแสดงออกมา พารามิเตอร์เหล่านี้จะกำหนดขอบเขตเป็นองค์ประกอบโฮสต์

ตัวเลือก CSS ที่ใช้ใน Shadow DOM จะมีผลกับคอมโพเนนต์ภายในเครื่อง ในทางปฏิบัติ เราจะสามารถใช้ชื่อรหัส/คลาสทั่วไปได้อีกครั้งโดยไม่ต้องกังวลเรื่องความขัดแย้งอื่นๆ ในหน้า ตัวเลือก CSS ที่ง่ายขึ้นคือแนวทางปฏิบัติแนะนำใน Shadow DOM นอกจากนี้ยังส่งผลดีต่อประสิทธิภาพด้วย

ตัวอย่าง - รูปแบบที่กำหนดไว้ในรากแสงเงาคือท้องถิ่น

#shadow-root
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        ...
    }
    #tabs {
        display: inline-flex;
        ...
    }
    </style>
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

สไตล์ชีตยังกำหนดขอบเขตเป็น Shadow Tree ด้วย:

#shadow-root
    <link rel="stylesheet" href="styles.css">
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

เคยสงสัยไหมว่าองค์ประกอบ <select> แสดงผลวิดเจ็ตแบบเลือกหลายรายการ (แทนเมนูแบบเลื่อนลง) ได้อย่างไรเมื่อคุณเพิ่มแอตทริบิวต์ multiple

<select multiple>
  <option>Do</option>
  <option selected>Re</option>
  <option>Mi</option>
  <option>Fa</option>
  <option>So</option>
</select>

<select> จัดรูปแบบตัวเองให้แตกต่างออกไปได้ตามแอตทริบิวต์ที่คุณประกาศ คอมโพเนนต์เว็บสามารถจัดรูปแบบได้เองเช่นกันโดยใช้ตัวเลือก :host

ตัวอย่าง - การจัดรูปแบบคอมโพเนนต์เอง

<style>
:host {
    display: block; /* by default, custom elements are display: inline */
    contain: content; /* CSS containment FTW. */
}
</style>

Gocha ที่มี :host อย่างแรกคือกฎในหน้าหลักมีความจำเพาะสูงกว่ากฎ :host ที่กำหนดไว้ในองค์ประกอบ นั่นคือ รูปแบบภายนอกจะชนะ วิธีนี้ช่วยให้ผู้ใช้ลบล้างการจัดรูปแบบระดับบนสุดจากภายนอกได้ นอกจากนี้ :host จะทำงานในบริบทของ Shadow Root เท่านั้น ดังนั้นจึงไม่สามารถใช้นอก Shadow DOM ได้

รูปแบบฟังก์ชันของ :host(<selector>) ช่วยให้คุณกำหนดเป้าหมายโฮสต์ได้หากตรงกับ <selector> นี่เป็นวิธีที่ยอดเยี่ยมสำหรับคอมโพเนนต์ในการห่อหุ้มลักษณะการทำงานที่ตอบสนองต่อการโต้ตอบของผู้ใช้ หรือสถานะ หรือจัดรูปแบบโหนดภายในโดยอิงตามโฮสต์

<style>
:host {
    opacity: 0.4;
    will-change: opacity;
    transition: opacity 300ms ease-in-out;
}
:host(:hover) {
    opacity: 1;
}
:host([disabled]) { /* style when host has disabled attribute. */
    background: grey;
    pointer-events: none;
    opacity: 0.4;
}
:host(.blue) {
    color: blue; /* color host when it has class="blue" */
}
:host(.pink) > #tabs {
    color: pink; /* color internal #tabs node when host has class="pink". */
}
</style>

การจัดรูปแบบตามบริบท

:host-context(<selector>) จะจับคู่คอมโพเนนต์หากคอมโพเนนต์หรือระดับบนตรงกับ <selector> ตัวอย่างที่พบบ่อยก็คือการกำหนดธีมโดยอิงตามสภาพแวดล้อมของคอมโพเนนต์ ตัวอย่างเช่น ผู้คนจำนวนมากสร้างธีมโดยใช้ชั้นเรียนกับ <html> หรือ <body>

<body class="darktheme">
    <fancy-tabs>
    ...
    </fancy-tabs>
</body>

:host-context(.darktheme) จะจัดรูปแบบ <fancy-tabs> เมื่อเป็นองค์ประกอบสืบทอดของ .darktheme:

:host-context(.darktheme) {
    color: white;
    background: black;
}

:host-context() อาจเป็นประโยชน์สำหรับการกำหนดธีม แต่วิธีที่ดีกว่าคือสร้าง Style hook โดยใช้พร็อพเพอร์ตี้ที่กำหนดเองของ CSS

การจัดรูปแบบโหนดแบบกระจาย

::slotted(<compound-selector>) จะจับคู่โหนดที่กระจายไปยัง <slot>

สมมติว่าเราสร้างคอมโพเนนต์ป้ายชื่อแล้ว

<name-badge>
    <h2>Eric Bidelman</h2>
    <span class="title">
    Digital Jedi, <span class="company">Google</span>
    </span>
</name-badge>

Shadow DOM ของคอมโพเนนต์สามารถจัดรูปแบบ <h2> และ .title ของผู้ใช้ได้:

<style>
::slotted(h2) {
    margin: 0;
    font-weight: 300;
    color: red;
}
::slotted(.title) {
    color: orange;
}
/* DOESN'T WORK (can only select top-level nodes).
::slotted(.company),
::slotted(.title .company) {
    text-transform: uppercase;
}
*/
</style>
<slot></slot>

หากจำได้ว่าก่อนหน้านี้ <slot> จะไม่ย้าย Light DOM ของผู้ใช้ เมื่อกระจายโหนดไปยัง <slot> แล้ว <slot> จะแสดงผล DOM ของตัวเอง แต่โหนดจะยังคงวางอยู่จริง รูปแบบที่ใช้ก่อนการเผยแพร่จะยังคง ใช้หลังการเผยแพร่ อย่างไรก็ตาม เมื่อมีการกระจาย Light DOM แล้ว Light DOM ก็สามารถใช้สไตล์เพิ่มเติม (รายการที่กำหนดโดย Shadow DOM) ได้

อีกตัวอย่างหนึ่งที่เจาะลึกมากขึ้นจาก <fancy-tabs>

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        border-radius: 3px;
        padding: 16px;
        height: 250px;
        overflow: auto;
    }
    #tabs {
        display: inline-flex;
        -webkit-user-select: none;
        user-select: none;
    }
    #tabsSlot::slotted(*) {
        font: 400 16px/22px 'Roboto';
        padding: 16px 8px;
        ...
    }
    #tabsSlot::slotted([aria-selected="true"]) {
        font-weight: 600;
        background: white;
        box-shadow: none;
    }
    #panelsSlot::slotted([aria-hidden="true"]) {
        display: none;
    }
    </style>
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot>
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>
`;

ในตัวอย่างนี้จะมีช่อง 2 ช่อง ได้แก่ ช่องที่มีชื่อสำหรับชื่อแท็บ และช่องสำหรับเนื้อหาแผงแท็บ เมื่อผู้ใช้เลือกแท็บ เราจะทำการเลือกเป็นตัวหนา และแสดงแผงของแท็บนั้น โดยเลือกโหนดแบบกระจายที่มีแอตทริบิวต์ selected JS ขององค์ประกอบที่กำหนดเอง (ไม่ได้แสดงที่นี่) จะเพิ่มแอตทริบิวต์นั้นในเวลาที่ถูกต้อง

การจัดรูปแบบคอมโพเนนต์จากภายนอก

การจัดรูปแบบคอมโพเนนต์จากภายนอกมี 2 วิธีดังนี้ วิธีที่ง่ายที่สุดคือการใช้ชื่อแท็กเป็นตัวเลือก

fancy-tabs {
    width: 500px;
    color: red; /* Note: inheritable CSS properties pierce the shadow DOM boundary. */
}
fancy-tabs:hover {
    box-shadow: 0 3px 3px #ccc;
}

รูปแบบภายนอกจะชนะรูปแบบที่กำหนดไว้ใน Shadow DOM เสมอ เช่น หากผู้ใช้เขียนตัวเลือก fancy-tabs { width: 500px; } ก็จะมีผลแทนกฎของคอมโพเนนต์: :host { width: 650px;}

การจัดรูปแบบคอมโพเนนต์เองจะทําให้คุณดูข้อมูลได้จนถึงขณะนี้ แต่จะเกิดอะไรขึ้นหากคุณต้องการ จัดรูปแบบภายในของคอมโพเนนต์ เราจึงต้องใช้พร็อพเพอร์ตี้ ที่กำหนดเองของ CSS

การสร้างฮุกรูปแบบโดยใช้พร็อพเพอร์ตี้ที่กำหนดเองของ CSS

ผู้ใช้จะปรับเปลี่ยนรูปแบบภายในได้หากผู้เขียนคอมโพเนนต์ใส่ฮุกการจัดรูปแบบโดยใช้คุณสมบัติที่กำหนดเองของ CSS โดยแนวคิดแล้ว แนวคิดจะคล้ายกับ <slot> คุณสร้าง "ตัวยึดตำแหน่งรูปแบบ" เพื่อให้ผู้ใช้ลบล้าง

ตัวอย่าง - <fancy-tabs> อนุญาตให้ผู้ใช้ลบล้างสีพื้นหลังได้

<!-- main page -->
<style>
    fancy-tabs {
    margin-bottom: 32px;
    --fancy-tabs-bg: black;
    }
</style>
<fancy-tabs background>...</fancy-tabs>

ภายใน Shadow DOM:

:host([background]) {
    background: var(--fancy-tabs-bg, #9E9E9E);
    border-radius: 10px;
    padding: 10px;
}

ในกรณีนี้ คอมโพเนนต์จะใช้ black เป็นค่าพื้นหลังเนื่องจากผู้ใช้ระบุ มิฉะนั้น ระบบจะใช้ค่าเริ่มต้นเป็น #9E9E9E

หัวข้อขั้นสูง

การสร้างรูทเงาแบบปิด (ควรหลีกเลี่ยง)

มี Shadow DOM อีกเวอร์ชันหนึ่งที่เรียกว่าโหมด "ปิด" เมื่อสร้างเงาต้นไม้แบบปิด ภายนอก JavaScript จะเข้าถึง DOM ภายในของคอมโพเนนต์ไม่ได้ ซึ่งคล้ายกับวิธีการทำงานขององค์ประกอบเนทีฟ เช่น <video> JavaScript เข้าถึง Shadow DOM ของ <video> ไม่ได้ เนื่องจากเบราว์เซอร์ติดตั้งใช้งานโดยใช้รูทเงาในโหมดปิด

ตัวอย่าง - การสร้างเงาต้นไม้แบบปิด

const div = document.createElement('div');
const shadowRoot = div.attachShadow({mode: 'closed'}); // close shadow tree
// div.shadowRoot === null
// shadowRoot.host === div

นอกจากนี้ API อื่นๆ ยังได้รับผลกระทบจากโหมดปิดด้วย

  • Element.assignedSlot / TextNode.assignedSlot ส่งคืน null
  • Event.composedPath() สำหรับเหตุการณ์ที่เชื่อมโยงกับองค์ประกอบภายในเงา DOM จะแสดงผล []

ต่อไปนี้เป็นข้อมูลสรุปเกี่ยวกับสาเหตุที่คุณไม่ควรสร้างคอมโพเนนต์ของเว็บด้วย {mode: 'closed'}

  1. ความปลอดภัยที่ประดิษฐ์ขึ้น ไม่มีสิ่งใดที่จะหยุดผู้โจมตีจากการลักลอบใช้บัญชี Element.prototype.attachShadow ได้

  2. โหมดปิดป้องกันไม่ให้โค้ดองค์ประกอบที่กำหนดเองเข้าถึง Shadow DOM ของตัวเอง เท่านี้ก็ไม่สำเร็จ หากต้องการใช้งานอย่าง querySelector() คุณจะต้องเก็บข้อมูลอ้างอิงไว้ในภายหลังแทน วิธีนี้ช่วยลบล้างจุดประสงค์ดั้งเดิมของโหมดปิดโดยสมบูรณ์

        customElements.define('x-element', class extends HTMLElement {
        constructor() {
        super(); // always call super() first in the constructor.
        this._shadowRoot = this.attachShadow({mode: 'closed'});
        this._shadowRoot.innerHTML = '<div class="wrapper"></div>';
        }
        connectedCallback() {
        // When creating closed shadow trees, you'll need to stash the shadow root
        // for later if you want to use it again. Kinda pointless.
        const wrapper = this._shadowRoot.querySelector('.wrapper');
        }
        ...
    });
    
  3. โหมดปิดจะทำให้คอมโพเนนต์มีความยืดหยุ่นน้อยลงสำหรับผู้ใช้ปลายทาง เมื่อสร้างคอมโพเนนต์เว็บ อาจมีบางเวลาที่คุณลืมเพิ่มฟีเจอร์ ตัวเลือกการกำหนดค่า กรณีการใช้งานที่ผู้ใช้ต้องการ ตัวอย่างทั่วไปคือลืมใส่ฮุกการจัดรูปแบบที่เพียงพอสำหรับโหนดภายใน เมื่อใช้โหมดปิด ผู้ใช้จะลบล้างรูปแบบเริ่มต้นและปรับแต่งไม่ได้ การมีสิทธิ์เข้าถึงองค์ประกอบภายในของคอมโพเนนต์จะมีประโยชน์อย่างมาก สุดท้าย ผู้ใช้จะแยกส่วนประกอบของคุณ ค้นหาส่วนประกอบอื่น หรือสร้างขึ้นมาเอง หากคอมโพเนนต์นั้นไม่ดำเนินการตามที่ต้องการ :(

การทำงานกับสล็อตใน JS

Shadow DOM API มีประโยชน์สำหรับการทำงานกับสล็อตและโหนดแบบกระจาย ข้อมูลเหล่านี้มีประโยชน์เมื่อเขียนองค์ประกอบที่กำหนดเอง

เหตุการณ์สล็อตการเปลี่ยนแปลง

เหตุการณ์ slotchange จะเริ่มทำงานเมื่อโหนดแบบกระจายของช่องมีการเปลี่ยนแปลง เช่น หากผู้ใช้เพิ่ม/นำเด็กออกจาก Light DOM

const slot = this.shadowRoot.querySelector('#slot');
slot.addEventListener('slotchange', e => {
    console.log('light dom children changed!');
});

หากต้องการตรวจสอบการเปลี่ยนแปลงประเภทอื่นๆ ของ Light DOM คุณสามารถตั้งค่า MutationObserver ในตัวสร้างขององค์ประกอบได้

องค์ประกอบใดบ้างที่กำลังแสดงผลในช่องโฆษณา

บางครั้งการรู้ว่าองค์ประกอบใดที่เชื่อมโยงกับช่องโฆษณาก็มีประโยชน์ เรียกใช้ slot.assignedNodes() เพื่อหาองค์ประกอบที่ช่องโฆษณากำลังแสดงผล ตัวเลือก {flatten: true} จะแสดงผลเนื้อหาสำรองของช่องด้วย (หากไม่มีการกระจายโหนด)

ตัวอย่างเช่น สมมติว่า Shadow DOM มีลักษณะดังนี้

<slot><b>fallback content</b></slot>
การใช้งานโทรผลลัพธ์
<my-component>ข้อความคอมโพเนนต์</my-component> slot.assignedNodes(); [component text]
<my-component></my-component> slot.assignedNodes(); []
<my-component></my-component> slot.assignedNodes({flatten: true}); [<b>fallback content</b>]

ช่องใดได้รับการกำหนดองค์ประกอบให้

หรือจะตอบคำถามย้อนกลับก็ได้เช่นกัน element.assignedSlot จะบอกคุณว่าองค์ประกอบใดที่คุณกำหนดให้กับช่องคอมโพเนนต์

โมเดลเหตุการณ์ Shadow DOM

เมื่อเหตุการณ์ปรากฏขึ้นจาก Shadow DOM เป้าหมายของเหตุการณ์จะปรับเพื่อรักษาการห่อหุ้มข้อมูลที่ Shadow DOM ให้ กล่าวคือ เหตุการณ์จะได้รับการกำหนดเป้าหมายใหม่ให้ดูเหมือนว่ามาจากคอมโพเนนต์ ไม่ใช่องค์ประกอบภายในภายใน Shadow DOM บางเหตุการณ์ยังไม่เผยแพร่จาก Shadow DOM

เหตุการณ์ที่ข้ามขอบเขตเงามีดังนี้

  • เหตุการณ์สำคัญ: blur, focus, focusin, focusout
  • เหตุการณ์ของเมาส์: click, dblclick, mousedown, mouseenter, mousemove ฯลฯ
  • เหตุการณ์ล้อ: wheel
  • เหตุการณ์อินพุต: beforeinput, input
  • เหตุการณ์ของแป้นพิมพ์: keydown, keyup
  • เหตุการณ์การเรียบเรียง: compositionstart, compositionupdate, compositionend
  • DragEvent: dragstart, drag, dragend, drop ฯลฯ

เคล็ดลับ

หากเงาต้นไม้เปิด การเรียกใช้ event.composedPath() จะแสดงผลอาร์เรย์ของโหนดที่เหตุการณ์เดินทางผ่าน

การใช้เหตุการณ์ที่กำหนดเอง

เหตุการณ์ DOM ที่กำหนดเองซึ่งเริ่มทำงานบนโหนดภายในใน Shadow Tree ไม่ปรากฏขึ้นนอกขอบเขตเงา เว้นแต่ว่าจะสร้างเหตุการณ์โดยใช้แฟล็ก composed: true

// Inside <fancy-tab> custom element class definition:
selectTab() {
    const tabs = this.shadowRoot.querySelector('#tabs');
    tabs.dispatchEvent(new Event('tab-select', {bubbles: true, composed: true}));
}

หากใช้ composed: false (ค่าเริ่มต้น) ผู้บริโภคจะไม่สามารถฟังเหตุการณ์ภายนอก Shadow Root ของคุณได้

<fancy-tabs></fancy-tabs>
<script>
    const tabs = document.querySelector('fancy-tabs');
    tabs.addEventListener('tab-select', e => {
    // won't fire if `tab-select` wasn't created with `composed: true`.
    });
</script>

การจัดการโฟกัส

หากคุณจำได้จากโมเดลเหตุการณ์ของ Shadow DOM เหตุการณ์ที่เริ่มทำงานภายใน Shadow DOM จะได้รับการปรับให้ดูเหมือนมาจากองค์ประกอบโฮสติ้ง ตัวอย่างเช่น สมมติว่าคุณคลิก <input> ภายในรากเงา

<x-focus>
    #shadow-root
    <input type="text" placeholder="Input inside shadow dom">

เหตุการณ์ focus จะดูเหมือนว่ามาจาก <x-focus> ไม่ใช่ <input> ในทำนองเดียวกัน document.activeElement จะเป็น <x-focus> หากสร้างเงามืดด้วย mode:'open' (ดูโหมดปิด) คุณจะเข้าถึงโหนดภายในที่ได้โฟกัสได้ด้วย

document.activeElement.shadowRoot.activeElement // only works with open mode.

หากมี Shadow DOM หลายระดับขณะเล่น (เช่น องค์ประกอบที่กำหนดเองภายในองค์ประกอบที่กำหนดเองอีกรายการ) คุณจะต้องเจาะลึกในรากของเงาซ้ำๆ เพื่อหา activeElement ดังนี้

function deepActiveElement() {
    let a = document.activeElement;
    while (a && a.shadowRoot && a.shadowRoot.activeElement) {
    a = a.shadowRoot.activeElement;
    }
    return a;
}

อีกตัวเลือกหนึ่งสำหรับการโฟกัสคือตัวเลือก delegatesFocus: true ซึ่งจะขยายพฤติกรรมการโฟกัสขององค์ประกอบภายในเงาต้นไม้

  • หากคุณคลิกโหนดภายใน Shadow DOM และโหนดไม่ใช่พื้นที่ที่โฟกัสได้ พื้นที่ที่โฟกัสได้ส่วนแรกจะเริ่มโฟกัส
  • เมื่อโหนดภายใน Shadow DOM ได้รับโฟกัส :focus จะนำไปใช้กับโฮสต์นอกเหนือจากองค์ประกอบที่โฟกัส

ตัวอย่าง - วิธีที่ delegatesFocus: true เปลี่ยนลักษณะการโฟกัส

<style>
    :focus {
    outline: 2px solid red;
    }
</style>

<x-focus></x-focus>

<script>
customElements.define('x-focus', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    const root = this.attachShadow({mode: 'open', delegatesFocus: true});
    root.innerHTML = `
        <style>
        :host {
            display: flex;
            border: 1px dotted black;
            padding: 16px;
        }
        :focus {
            outline: 2px solid blue;
        }
        </style>
        <div>Clickable Shadow DOM text</div>
        <input type="text" placeholder="Input inside shadow dom">`;

    // Know the focused element inside shadow DOM:
    this.addEventListener('focus', function(e) {
        console.log('Active element (inside shadow dom):',
                    this.shadowRoot.activeElement);
    });
    }
});
</script>

ผลลัพธ์

delegatesFocus: พฤติกรรมที่แท้จริง

ด้านบนคือผลลัพธ์เมื่อมีการโฟกัส <x-focus> (การคลิกของผู้ใช้ แท็บ focus() ฯลฯ) มีการคลิก "ข้อความ Shadow DOM ที่คลิกได้" หรือ <input> ภายในโฟกัสอยู่ (รวมถึง autofocus)

หากตั้งค่าเป็น delegatesFocus: false สิ่งที่คุณจะเห็นแทนมีดังนี้

delegatesFocus: false และโฟกัสอินพุตภายใน
delegatesFocus: false และ <input> ภายในโฟกัสอยู่
delegatesFocus: false และ x-Focus เพิ่มโฟกัส (เช่น มี Tabindex=&#39;0&#39;)
delegatesFocus: false และ <x-focus> ได้โฟกัส (เช่น มี tabindex="0")
delegatesFocus: มีการคลิก &quot;เท็จ&quot; และ &quot;ข้อความ Shadow DOM ที่คลิกได้&quot; (หรือมีการคลิกพื้นที่ว่างอื่นๆ ภายใน Shadow DOM ขององค์ประกอบ)
มีการคลิก delegatesFocus: false และ "ข้อความ Shadow DOM ที่คลิกได้" (หรือคลิกพื้นที่ว่างอื่นภายใน Shadow DOM ขององค์ประกอบ)

เคล็ดลับและคำแนะนำ

ตลอดหลายปีที่ผ่านมา ฉันได้เรียนรู้ 1-2 อย่างเกี่ยวกับการเขียนส่วนประกอบต่างๆ ของเว็บ เราคิดว่าเคล็ดลับเหล่านี้จะเป็นประโยชน์สำหรับการเขียนคอมโพเนนต์และการแก้ไขข้อบกพร่องของ Shadow DOM

ใช้การบรรจุ CSS

โดยปกติ เลย์เอาต์/รูปแบบ/สีของคอมโพเนนต์เว็บจะค่อนข้างอยู่ในตัวอยู่แล้ว ใช้การควบคุม CSS ใน :host เพื่อให้ได้ผลลัพธ์ที่ดียิ่งขึ้น:

<style>
:host {
    display: block;
    contain: content; /* Boom. CSS containment FTW. */
}
</style>

กำลังรีเซ็ตสไตล์ที่สืบทอดได้

สไตล์ที่รับช่วงได้ (background, color, font, line-height ฯลฯ) จะยังรับค่าใน Shadow DOM กล่าวคือ เป้าหมายเหล่านี้จะเจาะขอบเขต Shadow DOM โดยค่าเริ่มต้น หากต้องการเริ่มต้นด้วยแถบสเลทใหม่ ให้ใช้ all: initial; เพื่อรีเซ็ตสไตล์ที่รับช่วงมาได้เป็นค่าเริ่มต้นเมื่อข้ามขอบเขตเงา

<style>
    div {
    padding: 10px;
    background: red;
    font-size: 25px;
    text-transform: uppercase;
    color: white;
    }
</style>

<div>
    <p>I'm outside the element (big/white)</p>
    <my-element>Light DOM content is also affected.</my-element>
    <p>I'm outside the element (big/white)</p>
</div>

<script>
const el = document.querySelector('my-element');
el.attachShadow({mode: 'open'}).innerHTML = `
    <style>
    :host {
        all: initial; /* 1st rule so subsequent properties are reset. */
        display: block;
        background: white;
    }
    </style>
    <p>my-element: all CSS properties are reset to their
        initial value using <code>all: initial</code>.</p>
    <slot></slot>
`;
</script>

การค้นหาองค์ประกอบที่กำหนดเองทั้งหมดที่หน้าเว็บใช้

บางครั้งการค้นหาองค์ประกอบที่กำหนดเองที่ใช้ในหน้าเว็บก็มีประโยชน์ ในการทำเช่นนั้น คุณต้องข้ามผ่าน Shadow DOM ขององค์ประกอบทั้งหมดที่ใช้บนหน้าเว็บนั้นไปเรื่อยๆ

const allCustomElements = [];

function isCustomElement(el) {
    const isAttr = el.getAttribute('is');
    // Check for <super-button> and <button is="super-button">.
    return el.localName.includes('-') || isAttr && isAttr.includes('-');
}

function findAllCustomElements(nodes) {
    for (let i = 0, el; el = nodes[i]; ++i) {
    if (isCustomElement(el)) {
        allCustomElements.push(el);
    }
    // If the element has shadow DOM, dig deeper.
    if (el.shadowRoot) {
        findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
    }
    }
}

findAllCustomElements(document.querySelectorAll('*'));

การสร้างองค์ประกอบจาก <template>

แทนที่จะป้อน Shadow Root ด้วย .innerHTML เราใช้ <template> แบบประกาศ เทมเพลตเป็นตัวยึดตำแหน่งที่เหมาะสำหรับการประกาศโครงสร้างของคอมโพเนนต์เว็บ

ดูตัวอย่างใน "องค์ประกอบที่กำหนดเอง: การสร้างคอมโพเนนต์เว็บที่นำมาใช้ใหม่ได้"

การรองรับประวัติและเบราว์เซอร์

หากคุณติดตามคอมโพเนนต์ของเว็บในช่วง 2-3 ปีที่ผ่านมา คุณจะทราบว่า Chrome 35 ขึ้นไป/Opera ได้จัดส่ง Shadow DOM เวอร์ชันเก่ามาระยะหนึ่งแล้ว Blink จะยังรองรับทั้ง 2 เวอร์ชันพร้อมกันไประยะหนึ่ง ข้อกำหนด v0 ให้วิธีการอื่นในการสร้าง Shadow Root (element.createShadowRoot แทน element.attachShadow ของ v1) การเรียกใช้เมธอดที่เก่ากว่าจะยังคงสร้าง Shadow Root ที่ใช้ความหมาย v0 ดังนั้นโค้ด v0 ที่มีอยู่จะไม่หยุดทำงาน

หากคุณสนใจข้อกำหนด v0 แบบเก่า โปรดอ่านบทความ html5rocks 1, 2, 3 และยังมีการเปรียบเทียบความแตกต่างระหว่าง Shadow DOM v0 กับ v1 อีกด้วย

การสนับสนุนเบราว์เซอร์

Shadow DOM v1 จะจัดส่งใน Chrome 53 (สถานะ), Opera 40, Safari 10 และ Firefox 63 Edge ได้เริ่มการพัฒนา

หากต้องการฟีเจอร์การตรวจหา Shadow DOM ให้ตรวจสอบการมีอยู่ของ attachShadow:

const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;

ใยโพลีเอสเตอร์

จนกว่าการรองรับเบราว์เซอร์จะพร้อมให้บริการในวงกว้าง โพลีฟิล Shadydom และ Shadycss จะมอบฟีเจอร์ v1 ให้แก่คุณ Shady DOM จะเลียนแบบขอบเขตของ DOM ของ Shadow DOM และ Polyfills ของ Shadow DOM พร็อพเพอร์ตี้ที่กำหนดเองของ CSS และขอบเขตของรูปแบบที่ API เนทีฟมีให้

ติดตั้ง Polyfill:

bower install --save webcomponents/shadydom
bower install --save webcomponents/shadycss

ใช้โพลีฟิลล์ ดังนี้

function loadScript(src) {
    return new Promise(function(resolve, reject) {
    const script = document.createElement('script');
    script.async = true;
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
    });
}

// Lazy load the polyfill if necessary.
if (!supportsShadowDOMV1) {
    loadScript('/bower_components/shadydom/shadydom.min.js')
    .then(e => loadScript('/bower_components/shadycss/shadycss.min.js'))
    .then(e => {
        // Polyfills loaded.
    });
} else {
    // Native shadow dom v1 support. Go to go!
}

ดูวิธีการในการใช้/กำหนดสไตล์ของตัวเองได้ที่ https://github.com/webcomponents/shadycss#usage

บทสรุป

เป็นครั้งแรกที่เรามี API แบบพื้นฐานที่กำหนดขอบเขต CSS ที่เหมาะสม กำหนดขอบเขต DOM และมีการองค์ประกอบที่แท้จริง เมื่อใช้ร่วมกับ API คอมโพเนนต์เว็บอื่นๆ เช่น องค์ประกอบที่กำหนดเอง Shadow DOM จะช่วยให้เขียนคอมโพเนนต์ที่ห่อหุ้มไว้อย่างแท้จริงได้โดยไม่ต้องมีการแฮ็กหรือใช้สัมภาระรุ่นเก่าอย่าง <iframe>

ตอบผิดนะ Shadow DOM เป็นสัตว์ประหลาดที่ซับซ้อนอย่างยิ่ง แต่ก็เป็นการเรียนรู้ ที่คุ้มค่า ใช้เวลาสักระยะ เรียนรู้และถามคำถาม

อ่านเพิ่มเติม

คำถามที่พบบ่อย

วันนี้ฉันใช้ Shadow DOM v1 ได้ไหม

สำหรับโพลีฟิล ก็ใช้ได้นะ ดูการรองรับเบราว์เซอร์

Shadow DOM มีฟีเจอร์ด้านความปลอดภัยอะไรบ้าง

Shadow DOM ไม่ใช่ฟีเจอร์ความปลอดภัย เป็นเครื่องมือที่ใช้งานง่ายสำหรับการกำหนดขอบเขต CSS และซ่อนต้นไม้ DOM ในคอมโพเนนต์ หากต้องการขอบเขตความปลอดภัยที่แท้จริง ให้ใช้ <iframe>

คอมโพเนนต์เว็บต้องใช้ Shadow DOM ไหม

ไม่ คุณไม่จำเป็นต้องสร้างคอมโพเนนต์เว็บที่ใช้ Shadow DOM อย่างไรก็ตาม การสร้างองค์ประกอบที่กำหนดเองที่ใช้ Shadow DOM หมายความว่าคุณสามารถใช้ประโยชน์จากฟีเจอร์ต่างๆ เช่น การกำหนดขอบเขต CSS, การห่อหุ้ม DOM และองค์ประกอบได้

รากของเงาแบบเปิดและแบบปิดแตกต่างกันอย่างไร

โปรดดูหัวข้อรากเงาที่ปิดแล้ว