Shadow DOM ช่วยให้นักพัฒนาเว็บสร้าง DOM และ CSS ที่แยกเป็นส่วนๆ สำหรับคอมโพเนนต์เว็บได้
สรุป
Shadow DOM ช่วยลดความเปราะบางในการสร้างเว็บแอป ความเปราะบางนี้เกิดจากลักษณะโดยรวมของ HTML, CSS และ JS ตลอดหลายปีที่ผ่านมา เราได้คิดค้นเครื่องมือมากมายเพื่อหลีกเลี่ยงปัญหาต่างๆ ตัวอย่างเช่น เมื่อคุณใช้รหัส/คลาส HTML ใหม่
ก็ไม่มีทางรู้ได้เลยว่าจะขัดแย้งกับชื่อที่มีอยู่ซึ่งหน้าเว็บใช้อยู่หรือไม่
ข้อบกพร่องเล็กๆ น้อยๆ จะเพิ่มขึ้น ความเฉพาะเจาะจงของ CSS จะกลายเป็นปัญหาใหญ่ (!important ทุกอย่างเลย) ตัวเลือกสไตล์จะเพิ่มขึ้นจนควบคุมไม่ได้ และประสิทธิภาพอาจลดลง และอื่นๆ อีกมากมาย
Shadow DOM จะแก้ไข CSS และ DOM ซึ่งจะเพิ่มรูปแบบที่กำหนดขอบเขตลงในแพลตฟอร์มเว็บ คุณสามารถรวม CSS เข้ากับมาร์กอัป ซ่อนรายละเอียดการติดตั้งใช้งาน และเขียนคอมโพเนนต์แบบสมบูรณ์ในตัวใน Vanilla JavaScript ได้โดยไม่ต้องใช้เครื่องมือหรือรูปแบบการตั้งชื่อ
บทนำ
Shadow DOM เป็นหนึ่งในมาตรฐานคอมโพเนนต์เว็บ 3 รายการ ได้แก่ เทมเพลต HTML, Shadow DOM และ องค์ประกอบที่กำหนดเอง การนำเข้า HTML เคยเป็นส่วนหนึ่งของรายการ แต่ตอนนี้ถือว่าเลิกใช้งานแล้ว
คุณไม่จำเป็นต้องเขียนคอมโพเนนต์เว็บที่ใช้ Shadow DOM แต่เมื่อคุณใช้ 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
ตลอดทั้งบทความนี้ เราจะพูดถึงคอมโพเนนต์สาธิต (<fancy-tabs>) และอ้างอิงข้อมูลโค้ดจากคอมโพเนนต์ดังกล่าว หากเบราว์เซอร์รองรับ API คุณควรจะเห็นการสาธิตแบบสดด้านล่างนี้ หรือดูแหล่งที่มาแบบเต็มใน GitHub
Shadow DOM คืออะไร
ข้อมูลเบื้องหลังเกี่ยวกับ DOM
HTML ขับเคลื่อนเว็บเพราะใช้งานง่าย การประกาศแท็กเพียงไม่กี่แท็กจะช่วยให้คุณสร้างหน้าเว็บที่มีทั้งการนำเสนอและโครงสร้างได้ในไม่กี่วินาที อย่างไรก็ตาม HTML เพียงอย่างเดียวไม่ได้มีประโยชน์มากนัก มนุษย์เข้าใจภาษาที่อิงตามข้อความได้ง่าย แต่เครื่องจักรต้องการอะไรมากกว่านั้น ป้อนแบบจำลองออบเจ็กต์เอกสารหรือ 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 ที่กำหนดขอบเขตซึ่งแนบมากับองค์ประกอบ แต่แยกจากองค์ประกอบย่อยจริง ซับทรีย์ที่กำหนดขอบเขตนี้เรียกว่า Shadow Tree องค์ประกอบที่แนบอยู่คือ Shadow Host สิ่งที่คุณเพิ่มใน Shadow จะกลายเป็นส่วนหนึ่งขององค์ประกอบโฮสติ้ง ซึ่งรวมถึง <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 Root แต่คุณจะใช้ DOM API อื่นๆ ก็ได้ นี่คือเว็บ เรามีทางเลือก
ข้อกำหนดกำหนดรายการองค์ประกอบที่ไม่สามารถโฮสต์ Shadow Tree ได้ มีสาเหตุหลายประการที่ทำให้องค์ประกอบอยู่ในรายการ ดังนี้
- เบราว์เซอร์โฮสต์ 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 แต่ก็เป็นฟีเจอร์ที่สำคัญที่สุดอย่างปฏิเสธไม่ได้
ในโลกของการพัฒนาเว็บ Composition คือวิธีที่เราสร้างแอป
โดยใช้ HTML แบบประกาศ องค์ประกอบต่างๆ (<div>, <header>, <form>, <input>) จะรวมกันเพื่อสร้างแอป แท็กบางแท็กยังทำงานร่วมกันได้ด้วย Composition เป็นเหตุผลที่ทำให้องค์ประกอบแบบเนทีฟ เช่น <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 ของคุณ ซึ่งจะแสดงผลผลิตภัณฑ์ขั้นสุดท้าย โครงสร้างแบบแบนคือสิ่งที่คุณเห็นใน DevTools และสิ่งที่แสดงผลในหน้าเว็บ
<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 จะเป็นแบบเฉพาะที่
#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>
ข้อควรระวังอย่างหนึ่งเกี่ยวกับ :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() มีประโยชน์สำหรับการกำหนดธีม แต่วิธีที่ดีกว่าคือสร้างฮุกสไตล์โดยใช้พร็อพเพอร์ตี้ที่กำหนดเองของ 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 แบบ Light ของผู้ใช้ เมื่อโหนดกระจายไปยัง <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 Root แบบปิด (ควรหลีกเลี่ยง)
นอกจากนี้ยังมี Shadow DOM อีกรูปแบบหนึ่งที่เรียกว่าโหมด "ปิด" เมื่อสร้าง Shadow Tree แบบปิด JavaScript ภายนอกจะไม่สามารถเข้าถึง DOM ภายในของคอมโพเนนต์ได้ ซึ่งคล้ายกับวิธีการทำงานขององค์ประกอบเนทีฟ เช่น <video>
JavaScript ไม่สามารถเข้าถึง Shadow DOM ของ <video> เนื่องจากเบราว์เซอร์ใช้ Shadow Root แบบโหมดปิด
ตัวอย่าง - การสร้าง Shadow Tree แบบปิด
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ผลตอบแทนnullEvent.composedPath()สำหรับเหตุการณ์ที่เชื่อมโยงกับองค์ประกอบภายใน Shadow DOM จะแสดงผล []
สรุปเหตุผลที่คุณไม่ควรสร้างคอมโพเนนต์เว็บด้วย {mode: 'closed'} มีดังนี้
ความรู้สึกปลอดภัยที่สร้างขึ้น ไม่มีอะไรหยุดผู้โจมตีจากการลักลอบใช้
Element.prototype.attachShadowได้โหมดปิดจะป้องกันไม่ให้โค้ดองค์ประกอบที่กำหนดเองเข้าถึง 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'); } ... });โหมดปิดทำให้คอมโพเนนต์มีความยืดหยุ่นน้อยลงสำหรับผู้ใช้ปลายทาง เมื่อคุณสร้างคอมโพเนนต์เว็บ จะมีบางครั้งที่คุณลืมเพิ่มฟีเจอร์ ตัวเลือกการกำหนดค่า กรณีการใช้งานที่ผู้ใช้ต้องการ ตัวอย่างที่พบบ่อยคือการลืมใส่ฮุกการจัดสไตล์ที่เพียงพอสำหรับโหนดภายใน โหมดปิดจะทำให้ผู้ใช้ไม่สามารถลบล้างค่าเริ่มต้นและปรับแต่งสไตล์ได้ การเข้าถึงข้อมูลภายในของคอมโพเนนต์ได้นั้นมีประโยชน์มาก ท้ายที่สุดแล้ว ผู้ใช้จะแยกคอมโพเนนต์ของคุณ ค้นหาคอมโพเนนต์อื่น หรือสร้างคอมโพเนนต์ของตนเองหากคอมโพเนนต์นั้นไม่ทำงานตามที่ต้องการ :(
การทำงานกับช่องโฆษณาใน JS
Shadow DOM API มีเครื่องมือสำหรับการทำงานกับช่องและโหนดแบบกระจาย ซึ่งจะมีประโยชน์เมื่อเขียนองค์ประกอบที่กำหนดเอง
เหตุการณ์ slotchange
เหตุการณ์ 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>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 ด้วยซ้ำ
เหตุการณ์ที่ข้ามขอบเขตของ Shadow ได้แก่
- กิจกรรมที่โฟกัส:
blur,focus,focusin,focusout - เหตุการณ์เมาส์:
click,dblclick,mousedown,mouseenter,mousemoveฯลฯ - เหตุการณ์ Wheel:
wheel - เหตุการณ์อินพุต:
beforeinput,input - เหตุการณ์แป้นพิมพ์:
keydown,keyup - เหตุการณ์การเรียบเรียง:
compositionstart,compositionupdate,compositionend - DragEvent:
dragstart,drag,dragend,dropฯลฯ
เคล็ดลับ
หาก Shadow Tree เปิดอยู่ การเรียกใช้ event.composedPath() จะแสดงผลอาร์เรย์ของโหนดที่เหตุการณ์ผ่าน
การใช้เหตุการณ์ที่กำหนดเอง
เหตุการณ์ DOM ที่กำหนดเองซึ่งเริ่มทำงานในโหนดภายในใน Shadow Tree จะไม่แสดงนอกขอบเขต Shadow เว้นแต่จะสร้างเหตุการณ์โดยใช้แฟล็ก 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> ภายใน Shadow Root
<x-focus>
#shadow-root
<input type="text" placeholder="Input inside shadow dom">
กิจกรรม focus จะดูเหมือนมาจาก <x-focus> ไม่ใช่ <input>
ในทำนองเดียวกัน document.activeElement จะเป็น <x-focus> หากสร้าง Shadow Root ด้วย mode:'open' (ดูโหมดปิด) คุณจะเข้าถึงโหนดภายในที่ได้รับโฟกัสได้ด้วย ดังนี้
document.activeElement.shadowRoot.activeElement // only works with open mode.
หากมี Shadow DOM หลายระดับ (เช่น องค์ประกอบที่กำหนดเองภายในองค์ประกอบที่กำหนดเองอีกรายการ) คุณจะต้องเจาะลึกเข้าไปใน Shadow Root ซ้ำๆ เพื่อค้นหา activeElement ดังนี้
function deepActiveElement() {
let a = document.activeElement;
while (a && a.shadowRoot && a.shadowRoot.activeElement) {
a = a.shadowRoot.activeElement;
}
return a;
}
อีกตัวเลือกหนึ่งสำหรับการโฟกัสคือตัวเลือก delegatesFocus: true ซึ่งจะขยายลักษณะการโฟกัสขององค์ประกอบภายใน Shadow Tree ดังนี้
- หากคุณคลิกโหนดภายใน 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 ขององค์ประกอบ)
เคล็ดลับและคำแนะนำ
ตลอดหลายปีที่ผ่านมา ฉันได้เรียนรู้สิ่งต่างๆ เกี่ยวกับการเขียนคอมโพเนนต์เว็บ เราคิดว่าคุณจะพบว่าเคล็ดลับเหล่านี้มีประโยชน์สำหรับการเขียนคอมโพเนนต์และการแก้ไขข้อบกพร่องของ 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; เพื่อรีเซ็ตสไตล์ที่รับค่าได้เป็นค่าเริ่มต้นเมื่อข้ามขอบเขต Shadow
<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>
เราสามารถใช้ <template> แบบประกาศแทนการสร้าง Shadow Root โดยใช้ .innerHTML ได้ เทมเพลตเป็นตัวยึดตำแหน่งที่เหมาะสำหรับการประกาศโครงสร้างของคอมโพเนนต์เว็บ
ดูตัวอย่างใน "องค์ประกอบที่กำหนดเอง: การสร้างคอมโพเนนต์เว็บที่นำกลับมาใช้ใหม่ได้"
การรองรับประวัติและการรองรับเบราว์เซอร์
หากคุณติดตามคอมโพเนนต์เว็บในช่วง 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;
Polyfill
จนกว่าเบราว์เซอร์จะรองรับอย่างแพร่หลาย Polyfill ของ shadydom และ shadycss จะให้ฟีเจอร์ v1 แก่คุณ Shady DOM เลียนแบบขอบเขต DOM ของ Shadow DOM และ ShadyCSS Polyfill คุณสมบัติที่กำหนดเองของ 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
บทสรุป
นี่เป็นครั้งแรกที่เรามี API พื้นฐานที่กำหนดขอบเขต CSS, กำหนดขอบเขต DOM และมีองค์ประกอบที่แท้จริง เมื่อใช้ร่วมกับ API คอมโพเนนต์เว็บอื่นๆ เช่น องค์ประกอบที่กำหนดเอง Shadow DOM จะช่วยให้คุณเขียนคอมโพเนนต์ที่ได้รับการแคปซูลอย่างแท้จริงโดยไม่ต้องแฮ็กหรือใช้ข้อมูลเก่าๆ เช่น <iframe>
อย่าเข้าใจฉันผิด Shadow DOM เป็นสิ่งที่ซับซ้อนอย่างแน่นอน แต่ก็เป็นเรื่องที่คุ้มค่าที่จะเรียนรู้ ใช้เวลากับเรื่องนี้สักหน่อย เรียนรู้และถามคำถามเลย
อ่านเพิ่มเติม
- ความแตกต่างระหว่าง Shadow DOM v1 และ v0
- "Introducing Slot-Based Shadow DOM API" จากบล็อก WebKit
- Web Components and the future of Modular CSS โดย Philip Walton
- "องค์ประกอบที่กำหนดเอง: การสร้างคอมโพเนนต์เว็บที่นำกลับมาใช้ใหม่ได้" จาก WebFundamentals ของ Google
- ข้อกำหนด Shadow DOM v1
- ข้อกำหนดองค์ประกอบที่กำหนดเอง v1
คำถามที่พบบ่อย
ฉันจะใช้ Shadow DOM v1 วันนี้ได้ไหม
ได้ หากใช้ Polyfill ดูการรองรับเบราว์เซอร์
Shadow DOM มีฟีเจอร์ความปลอดภัยอะไรบ้าง
Shadow DOM ไม่ใช่ฟีเจอร์ความปลอดภัย ซึ่งเป็นเครื่องมือขนาดเล็กสำหรับกำหนดขอบเขต CSS
และซ่อนแผนผัง DOM ในคอมโพเนนต์ หากต้องการขอบเขตความปลอดภัยที่แท้จริง ให้ใช้ <iframe>
คอมโพเนนต์เว็บต้องใช้ Shadow DOM ไหม
ไม่ คุณไม่จำเป็นต้องสร้างคอมโพเนนต์เว็บที่ใช้ Shadow DOM อย่างไรก็ตาม การเขียนองค์ประกอบที่กำหนดเองซึ่งใช้ Shadow DOM หมายความว่าคุณจะใช้ประโยชน์จากฟีเจอร์ต่างๆ เช่น การกำหนดขอบเขต CSS, การห่อหุ้ม DOM และการจัดองค์ประกอบได้
Shadow Root แบบเปิดและแบบปิดแตกต่างกันอย่างไร