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

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

สรุป

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

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

บทนำ

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

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

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

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

fancy-tabs demo

ตลอดทั้งบทความนี้ เราจะอ้างอิงถึงคอมโพเนนต์สาธิต (<fancy-tabs>) และข้อมูลโค้ดที่อ้างอิงจากคอมโพเนนต์ดังกล่าว หากเบราว์เซอร์ของคุณรองรับ API คุณจะเห็นการสาธิตการใช้งานจริงของ 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 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 เพื่อกรอกข้อมูลรูทเงา แต่คุณใช้ 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 เนื่องจากเรากําลังสร้างรูทเงา กฎ 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 นี้อยู่นอก Shadow 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 ของคอมโพเนนต์ พูดง่ายๆ คือคุณกําลังพูดว่า "แสดงผลมาร์กอัปของผู้ใช้ที่นี่"

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

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

การจัดสไตล์คอมโพเนนต์เว็บทำได้หลายวิธี คอมโพเนนต์ที่ใช้ Shadow 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-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>

ข้อควรระวังอย่างหนึ่งเกี่ยวกับ :host คือกฎในหน้าหลักมีความเฉพาะเจาะจงสูงกว่ากฎ :host ที่กําหนดไว้ในองค์ประกอบ กล่าวคือ รูปแบบภายนอกจะชนะ ซึ่งจะช่วยให้ผู้ใช้ลบล้างการจัดรูปแบบระดับบนสุดจากภายนอกได้ นอกจากนี้ :host จะใช้งานได้ในบริบทของ Shadow Root เท่านั้น คุณจึงใช้ :host นอก 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() อาจมีประโยชน์สำหรับธีม แต่วิธีที่ดีกว่าคือสร้างฮุกรูปแบบโดยใช้พร็อพเพอร์ตี้ที่กำหนดเองของ 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> จะไม่ย้าย DOM ของแสงของผู้ใช้ เมื่อมีการกระจายโหนดไปยัง <slot> <slot> จะแสดงผล 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 ยังมีอีกรูปแบบหนึ่งที่เรียกว่าโหมด "ปิด" เมื่อคุณสร้างทรี Shadow แบบปิด ภายนอก JavaScript จะเข้าถึง DOM ภายในของคอมโพเนนต์ไม่ได้ ซึ่งคล้ายกับวิธีการทํางานขององค์ประกอบเนทีฟ เช่น <video> JavaScript เข้าถึง Shadow DOM ของ <video> ไม่ได้เนื่องจากเบราว์เซอร์ใช้ Shadow Root โหมดปิด

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

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() สำหรับเหตุการณ์ที่เชื่อมโยงกับองค์ประกอบภายใน Shadow 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

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

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

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

องค์ประกอบใดบ้างที่แสดงผลในช่อง

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

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

<slot><b>fallback content</b></slot>
การใช้งานโทรผลลัพธ์
<my-component>component text</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 ที่กําหนดเองซึ่งเรียกใช้ที่โหนดภายในในต้นไม้เงาจะไม่แสดงขึ้นนอกขอบเขตเงา เว้นแต่ว่าเหตุการณ์จะสร้างขึ้นโดยใช้ Flag 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 (ค่าเริ่มต้น) ผู้บริโภคจะไม่สามารถฟังเหตุการณ์นอกรูทเงาได้

<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> (ผู้ใช้คลิก กด Tab ไปที่ 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: false และมีการคลิก &quot;ข้อความ Shadow DOM ที่คลิกได้&quot; (หรือมีการคลิกพื้นที่ว่างอื่นๆ ภายใน Shadow DOM ขององค์ประกอบ)
delegatesFocus: false และมีการคลิก "ข้อความ Shadow DOM ที่คลิกได้" (หรือมีการคลิกพื้นที่ว่างอื่นๆ ภายใน Shadow DOM ขององค์ประกอบ)

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

ตลอดหลายปีที่ผ่านมา เราเรียนรู้สิ่งเล็กๆ น้อยๆ เกี่ยวกับการเขียนคอมโพเนนต์เว็บ เราคิดว่าเคล็ดลับเหล่านี้บางส่วนจะมีประโยชน์สําหรับการเขียนคอมโพเนนต์และการแก้ไขข้อบกพร่อง 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>

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

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

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

หากติดตามเว็บคอมโพเนนต์ในช่วง 2-3 ปีที่ผ่านมา คุณคงทราบว่า Chrome 35 ขึ้นไปและ Opera ใช้งาน Shadow DOM เวอร์ชันเก่ามาระยะหนึ่งแล้ว Blink จะยังคงรองรับทั้ง 2 เวอร์ชันควบคู่กันไปอีกระยะหนึ่ง ข้อกําหนดของ v0 มีวิธีการอื่นในการสร้างรูทเงา (element.createShadowRoot แทนที่จะเป็น element.attachShadow ของ v1) การเรียกใช้เมธอดเก่าจะยังคงสร้างรูทเงาด้วยความหมายของ 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 polyfill จะช่วยให้คุณใช้งานฟีเจอร์เวอร์ชัน 1 ได้ Shady DOM เลียนแบบการกําหนดขอบเขต DOM ของ Shadow DOM และ polyfill ของ shadycss พร็อพเพอร์ตี้ที่กําหนดเองของ CSS และการกําหนดขอบเขตสไตล์ที่ API เดิมมีให้

ติดตั้ง polyfill ดังนี้

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

ใช้ polyfill ต่อไปนี้

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!
}

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

บทสรุป

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

อย่าเข้าใจผิด Shadow DOM เป็นสิ่งที่ซับซ้อนมาก แต่เครื่องมือนี้มีประโยชน์มาก ใช้เวลาสักพัก เรียนรู้และถามคําถาม

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

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

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

ใช่ เมื่อใช้ polyfill ดูการรองรับเบราว์เซอร์

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

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

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

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

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

ดูรูทเงาที่ปิดอยู่