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

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

สรุป

Image for: สรุป

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

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

บทนำ

Image for: บทนำ

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 คืออะไร

Image for: 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

Image for: การสร้าง 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>

การประพันธ์เพลงและช่อง

Image for: การประพันธ์เพลงและช่อง

การคอมโพสิชันเป็นหนึ่งในฟีเจอร์ของ 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> ทำ

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

Image for: การจัดรูปแบบ

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

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

Image for: หัวข้อขั้นสูง

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

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>

ผลลัพธ์

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

หากตั้งค่าเป็น delegatesFocus: false คุณจะเห็นข้อมูลต่อไปนี้แทน

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

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

Image for: เคล็ดลับและคำแนะนำ

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

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

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

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

หากติดตามเว็บคอมโพเนนต์ในช่วง 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!
}

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

บทสรุป

Image for: บทสรุป

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

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

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

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

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

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

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

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

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

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

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

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

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