แนวคิดขั้นสูงและ DOM API
บทความนี้กล่าวถึงสิ่งมหัศจรรย์อีกมากมายที่คุณสามารถทำได้ด้วย Shadow DOM ซึ่งจะต่อยอดจากแนวคิดที่กล่าวถึงใน Shadow DOM 101 และ Shadow DOM 201
การใช้รากแสงหลายราก
หากคุณจัดงานปาร์ตี้ ทุกคนจะอึดอัดหากต้องเบียดกันอยู่ในห้องเดียวกัน คุณต้องการตัวเลือกในการกระจายกลุ่มคนไปยังหลายห้อง องค์ประกอบที่โฮสต์ Shadow DOM สามารถทำเช่นนี้ได้เช่นกัน กล่าวคือ องค์ประกอบดังกล่าวสามารถโฮสต์ Shadow Root ได้มากกว่า 1 รายการพร้อมกัน
เรามาดูกันว่าจะเกิดอะไรขึ้นหากพยายามแนบรูทเงาหลายรายการกับโฮสต์
<div id="example1">Light DOM</div>
<script>
var container = document.querySelector('#example1');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div>';
root2.innerHTML = '<div>Root 2 FTW</div>';
</script>
สิ่งที่แสดงผลคือ "Root 2 FTW" แม้ว่าเราจะแนบ Shadow Tree ไว้แล้วก็ตาม เนื่องจากระบบจะเลือก Shadow Tree รายการล่าสุดที่เพิ่มลงในโฮสต์ นี่เป็นกอง LIFO ในแง่ของการเรนเดอร์ ซึ่งการตรวจสอบเครื่องมือสำหรับนักพัฒนาเว็บจะยืนยันลักษณะการทำงานนี้
แล้วการใช้เงาหลายรายการจะมีประโยชน์อะไรหากมีเพียงเงาสุดท้ายเท่านั้นที่เชิญให้เข้าร่วมการเรนเดอร์ ป้อนจุดแทรกเงา
จุดแทรกเงา
"จุดแทรกเงา" (<shadow>
) คล้ายกับจุดแทรก (<content>
) ปกติตรงที่เป็นตัวยึดตำแหน่ง อย่างไรก็ตาม ไฟล์เหล่านี้ไม่ได้เป็นตัวยึดตําแหน่งสําหรับเนื้อหาของโฮสต์ แต่เป็นโฮสต์สําหรับต้นไม้เงาอื่นๆ
พบกับ Shadow DOM Inception
ดังที่คุณอาจทราบแล้วว่ายิ่งเจาะลึกลงไปมากเท่าใด ทุกอย่างก็จะยิ่งซับซ้อนมากขึ้นเท่านั้น ด้วยเหตุนี้ ข้อกำหนดจึงระบุไว้อย่างชัดเจนว่าจะเกิดอะไรขึ้นเมื่อมีองค์ประกอบ <shadow>
หลายรายการ
เมื่อมองกลับไปที่ตัวอย่างแรก root1
แรกไม่ได้อยู่ในรายการคำเชิญ การเพิ่มจุดแทรก <shadow>
จะนำจุดแทรกกลับมา
<div id="example2">Light DOM</div>
<script>
var container = document.querySelector('#example2');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div><content></content>';
**root2.innerHTML = '<div>Root 2 FTW</div><shadow></shadow>';**
</script>
มีสิ่งที่น่าสนใจ 2 อย่างเกี่ยวกับตัวอย่างนี้
- "Root 2 FTW" จะยังคงแสดงผลเหนือ "Root 1 FTW" สาเหตุคือตําแหน่งที่เราวางจุดแทรก
<shadow>
หากต้องการแบบย้อนกลับ ให้ย้ายจุดแทรก:root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';
- โปรดสังเกตว่าตอนนี้มีจุดแทรก
<content>
ใน root1 ซึ่งจะทำให้โหนดข้อความ "Light DOM" แสดงผลด้วย
สิ่งที่แสดงผลที่ <shadow>
บางครั้งการทราบว่าแผนผังเงาที่เก่ากว่าแสดงใน <shadow>
ก็มีประโยชน์ คุณดูข้อมูลอ้างอิงสำหรับแผนภูมิดังกล่าวผ่าน .olderShadowRoot
ได้โดยทำดังนี้
**root2.olderShadowRoot** === root1 //true
การขอรูทเงาของโฮสต์
หากองค์ประกอบโฮสต์ Shadow DOM อยู่ คุณจะเข้าถึงรูท Shadow ที่อายุน้อยที่สุดได้โดยใช้ .shadowRoot
ดังนี้
var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null
หากคุณกังวลว่าจะมีบุคคลเดินผ่านเงาของคุณ ให้กําหนดค่า .shadowRoot
เป็น Null ดังนี้
Object.defineProperty(host, 'shadowRoot', {
get: function() { return null; },
set: function(value) { }
});
เป็นการแฮ็กแบบเล็กน้อย แต่ได้ผล สุดท้ายนี้ โปรดทราบว่าแม้ Shadow DOM จะยอดเยี่ยมอย่างไม่น่าเชื่อ แต่ก็ไม่ได้ออกแบบมาเพื่อเป็นฟีเจอร์ด้านความปลอดภัย แต่อย่าพึ่งพาการแยกเนื้อหาอย่างสมบูรณ์
การสร้าง Shadow DOM ใน JS
หากต้องการสร้าง DOM ใน JS HTMLContentElement
และ HTMLShadowElement
มีอินเทอร์เฟซสําหรับการดำเนินการดังกล่าว
<div id="example3">
<span>Light DOM</span>
</div>
<script>
var container = document.querySelector('#example3');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
var div = document.createElement('div');
div.textContent = 'Root 1 FTW';
root1.appendChild(div);
// HTMLContentElement
var content = document.createElement('content');
content.select = 'span'; // selects any spans the host node contains
root1.appendChild(content);
var div = document.createElement('div');
div.textContent = 'Root 2 FTW';
root2.appendChild(div);
// HTMLShadowElement
var shadow = document.createElement('shadow');
root2.appendChild(shadow);
</script>
ตัวอย่างนี้เกือบจะเหมือนกับตัวอย่างในส่วนก่อนหน้า
ความแตกต่างเพียงอย่างเดียวคือตอนนี้ฉันใช้ select
เพื่อดึง <span>
ที่เพิ่มเข้ามาใหม่
การทำงานกับจุดแทรก
โหนดที่เลือกจากองค์ประกอบโฮสต์และ "กระจาย" ไปยังต้นไม้เงานั้นเรียกกันว่าโหนดแบบกระจาย ข้อความจะข้ามขอบเขตเงาได้เมื่อจุดแทรกเชิญให้เข้ามา
แนวคิดที่แปลกไปมากเกี่ยวกับจุดแทรกคือ จุดเหล่านั้นไม่ได้ย้าย DOM โดยโหนดของโฮสต์จะยังคงอยู่เหมือนเดิม จุดแทรกเป็นเพียงการโปรเจ็กต์โหนดจากโฮสต์ไปยังต้นไม้เงาอีกครั้ง เป็นการนำเสนอ/การแสดงผล: "ย้ายโหนดเหล่านี้ไปที่นี่" "แสดงผลโหนดเหล่านี้ที่ตำแหน่งนี้"
เช่น
<div><h2>Light DOM</h2></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<content select="h2"></content>';
var h2 = document.querySelector('h2');
console.log(root.querySelector('content[select="h2"] h2')); // null;
console.log(root.querySelector('content').contains(h2)); // false
</script>
เรียบร้อย! h2
ไม่ใช่รายการย่อยของ Shadow DOM ซึ่งนำไปสู่เรื่องที่น่าสนใจอีกเรื่องหนึ่ง
Element.getDistributedNodes()
เราข้ามผ่านไปยัง <content>
ไม่ได้ แต่ .getDistributedNodes()
API ช่วยให้เราค้นหาโหนดแบบกระจายที่จุดแทรกได้
<div id="example4">
<h2>Eric</h2>
<h2>Bidelman</h2>
<div>Digital Jedi</div>
<h4>footer text</h4>
</div>
<template id="sdom">
<header>
<content select="h2"></content>
</header>
<section>
<content select="div"></content>
</section>
<footer>
<content select="h4:first-of-type"></content>
</footer>
</template>
<script>
var container = document.querySelector('#example4');
var root = container.createShadowRoot();
var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);
var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
html.push(el.outerHTML + ': ');
var nodes = el.getDistributedNodes();
[].forEach.call(nodes, function(node) {
html.push(node.outerHTML);
});
html.push('\n');
});
</script>
Element.getDestinationInsertionPoints()
คุณสามารถตรวจสอบจุดแทรกที่โหนดกระจายอยู่ได้ด้วยการเรียก.getDestinationInsertionPoints()
ของโหนด เช่นเดียวกับ.getDistributedNodes()
<div id="host">
<h2>Light DOM
</div>
<script>
var container = document.querySelector('div');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<content select="h2"></content>';
root2.innerHTML = '<shadow></shadow>';
var h2 = document.querySelector('#host h2');
var insertionPoints = h2.getDestinationInsertionPoints();
[].forEach.call(insertionPoints, function(contentEl) {
console.log(contentEl);
});
</script>
เครื่องมือ: เครื่องมือแสดงภาพ Shadow DOM
การทำความเข้าใจเวทมนตร์ดำที่เป็น Shadow DOM นั้นเป็นเรื่องยาก เราจำได้ว่าพยายามทำความเข้าใจเรื่องนี้เป็นครั้งแรก
เราได้สร้างเครื่องมือโดยใช้ d3.js เพื่อช่วยให้เห็นภาพว่าการแสดงผล Shadow DOM ทํางานอย่างไร ช่องมาร์กอัปทั้ง 2 ช่องทางด้านซ้ายจะแก้ไขได้ วางมาร์กอัปของคุณเองและลองดูว่าระบบทํางานอย่างไร และจุดแทรกจะเปลี่ยนโฮสต์โหนดเป็นต้นไม้เงาอย่างไร
ลองใช้และบอกให้เราทราบว่าคุณคิดอย่างไร
โมเดลเหตุการณ์
เหตุการณ์บางรายการข้ามขอบเขตเงา แต่บางรายการไม่ข้าม ในกรณีที่เหตุการณ์ข้ามขอบเขต ระบบจะปรับเป้าหมายเหตุการณ์เพื่อรักษาการรวมกลุ่มที่ขอบเขตบนของรูทเงาให้อยู่ กล่าวคือ ระบบจะกําหนดเป้าหมายเหตุการณ์ใหม่ให้ดูเหมือนว่ามาจากองค์ประกอบโฮสต์ ไม่ใช่องค์ประกอบภายในของ Shadow DOM
เล่นการกระทำ 1
- อันนี้น่าสนใจ คุณควรเห็น
mouseout
จากองค์ประกอบโฮสต์ (<div data-host>
) ไปยังโหนดสีน้ำเงิน แม้ว่าจะเป็นโหนดแบบกระจาย แต่โหนดดังกล่าวจะยังคงอยู่ในโฮสต์ ไม่ใช่ ShadowDOM การเลื่อนเมาส์ลงไปจนเป็นสีเหลืองอีกครั้งจะทำให้เกิดmouseout
บนโหนดสีน้ำเงิน
Play Action 2
- มี
mouseout
1 ตัวที่ปรากฏในโฮสต์ (ที่ส่วนท้ายสุด) โดยปกติแล้วคุณจะเห็นเหตุการณ์mouseout
ทริกเกอร์สำหรับบล็อกสีเหลืองทั้งหมด อย่างไรก็ตาม ในกรณีนี้ องค์ประกอบเหล่านี้อยู่ภายใน Shadow DOM และเหตุการณ์จะไม่ทวีไปตามขอบเขตด้านบน
เล่นการดําเนินการ 3
- โปรดสังเกตว่าเมื่อคุณคลิกอินพุต
focusin
จะไม่ปรากฏในอินพุต แต่จะไม่ปรากฏในโหนดของโฮสต์ เปลี่ยนเป้าหมายแล้ว
เหตุการณ์ที่หยุดอยู่เสมอ
เหตุการณ์ต่อไปนี้จะไม่ข้ามขอบเขตเงา
- ล้มเลิก
- ข้อผิดพลาด
- เลือก
- เปลี่ยน
- โหลด
- รีเซ็ต
- ปรับขนาด
- scroll
- selectstart
บทสรุป
เราหวังว่าคุณจะเห็นด้วยว่า Shadow DOM มีประสิทธิภาพอย่างมาก เรามีการบรรจุที่เหมาะสมเป็นครั้งแรกโดยไม่มี<iframe>
หรือเทคนิคเก่าๆ อื่นๆ เพิ่มเติม
Shadow DOM เป็นเทคโนโลยีที่ซับซ้อน แต่ก็เป็นเทคโนโลยีที่ควรเพิ่มลงในแพลตฟอร์มเว็บ ลองใช้เวลาสักครู่ เรียนรู้ ถามคำถาม
หากต้องการดูข้อมูลเพิ่มเติม โปรดอ่านบทความแนะนำของ Dominic เรื่อง Shadow DOM 101 และบทความ Shadow DOM 201: CSS และการจัดสไตล์ของฉัน