Konsep lanjutan & DOM API
Artikel ini membahas lebih banyak hal menakjubkan yang dapat Anda lakukan dengan Shadow DOM! Tutorial ini dibuat berdasarkan konsep yang dibahas di Shadow DOM 101 dan Shadow DOM 201.
Menggunakan beberapa akar bayangan
Jika Anda menyelenggarakan pesta, suasananya akan penuh jika semua orang berdesakan di ruangan yang sama. Anda menginginkan opsi untuk mendistribusikan kelompok orang di beberapa ruangan. Elemen yang menghosting Shadow DOM juga dapat melakukannya, yaitu dapat menghosting lebih dari satu shadow root sekaligus.
Mari kita lihat apa yang terjadi jika kita mencoba melampirkan beberapa root bayangan ke host:
<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>
Yang dirender adalah "Root 2 FTW", terlepas dari kenyataan bahwa kita telah melampirkan shadow tree. Ini karena pohon bayangan terakhir yang ditambahkan ke {i>host<i}, menang. Ini adalah tumpukan LIFO sejauh yang berkaitan dengan rendering. Memeriksa DevTools akan memverifikasi perilaku ini.
Jadi, apa gunanya menggunakan beberapa bayangan jika hanya yang terakhir yang diundang ke pihak rendering? Masukkan titik penyisipan bayangan.
Titik Penyisipan Bayangan
"Titik penyisipan bayangan" (<shadow>
) mirip dengan titik penyisipan normal (<content>
) yang merupakan placeholder. Namun, host untuk hierarki bayangan lainnya tidak menjadi placeholder untuk konten host.
Inilah Inception Shadow DOM!
Seperti yang mungkin dapat Anda bayangkan, segalanya akan menjadi lebih rumit jika Anda melihat perincian lebih dalam lagi. Karena alasan ini, spesifikasinya sangat jelas tentang apa yang terjadi saat
beberapa elemen <shadow>
diputar:
Melihat kembali contoh asli kita, root1
bayangan pertama ditinggalkan dari daftar undangan. Menambahkan titik penyisipan <shadow>
akan mengembalikan titik tersebut:
<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>
Ada beberapa hal menarik tentang contoh ini:
- "Root 2 FTW" masih dirender di atas "Root 1 FTW". Ini karena tempat kita menempatkan
titik penyisipan
<shadow>
. Jika Anda menginginkan hal sebaliknya, pindahkan titik penyisipan:root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';
. - Perhatikan bahwa sekarang ada titik penyisipan
<content>
di root1. Hal ini membuat node teks "Light DOM" muncul untuk perjalanan rendering.
Apa yang dirender pada <shadow>
?
Terkadang, ada baiknya mengetahui pohon bayangan lama yang dirender di <shadow>
. Anda bisa mendapatkan referensi ke hierarki tersebut melalui .olderShadowRoot
:
**root2.olderShadowRoot** === root1 //true
Memperoleh root bayangan host
Jika elemen menghosting Shadow DOM, Anda dapat mengakses shadow root termuda
menggunakan .shadowRoot
:
var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null
Jika Anda khawatir orang-orang masuk ke dalam bayangan Anda, tentukan ulang .shadowRoot
menjadi null:
Object.defineProperty(host, 'shadowRoot', {
get: function() { return null; },
set: function(value) { }
});
Sedikit tips, tapi cara ini bisa. Pada akhirnya, penting untuk diingat bahwa meskipun luar biasa fantastis, Shadow DOM tidak dirancang untuk menjadi fitur keamanan. Jangan mengandalkannya untuk isolasi konten lengkap.
Membangun DOM Bayangan di JS
Jika Anda lebih suka membuat DOM di JS, HTMLContentElement
dan HTMLShadowElement
memiliki antarmuka untuk hal tersebut.
<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>
Contoh ini hampir identik dengan contoh di bagian sebelumnya.
Satu-satunya perbedaan adalah sekarang saya menggunakan select
untuk mengambil <span>
yang baru ditambahkan.
Menggunakan titik penyisipan
Node yang dipilih dari elemen host dan "distribute" ke dalam shadow tree disebut...drumroll...node terdistribusi! Mereka diizinkan melewati batas bayangan saat titik penyisipan mengundang mereka.
Yang secara konseptual aneh tentang titik penyisipan adalah titik penyisipan tersebut tidak secara fisik
memindahkan DOM. {i>Node<i} {i>host<i} tetap utuh. Titik penyisipan hanya memproyeksikan ulang node
dari host ke pohon bayangan. Ini adalah hal presentasi/rendering: "Pindahkan node ini ke sini" "Render node ini di lokasi ini".
Contoh:
<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>
Hasilnya? h2
bukan turunan shadow DOM. Ini mengarah ke tid bit lain:
Element.getDistributedNodes()
Kita tidak dapat melintas ke <content>
, tetapi .getDistributedNodes()
API
memungkinkan kita untuk membuat kueri node terdistribusi pada titik penyisipan:
<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()
Serupa dengan .getDistributedNodes()
, Anda dapat memeriksa titik penyisipan
yang didistribusikan node dengan memanggil .getDestinationInsertionPoints()
:
<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>
Alat: Shadow DOM Visualizer
Memahami ilmu hitam yang merupakan Shadow DOM tidaklah mudah. Saya ingat ketika saya mencoba memikirkan penggunaannya untuk pertama kalinya.
Untuk membantu memvisualisasikan cara kerja rendering Shadow DOM, saya telah membuat alat menggunakan d3.js. Kedua kotak markup di sebelah kiri dapat diedit. Jangan ragu untuk menempelkan markup Anda sendiri dan bermain-main untuk melihat cara kerja berbagai hal dan titik penyisipan melakukan swizzle node host ke shadow tree.
Cobalah dan beri tahu saya pendapat Anda.
Model Peristiwa
Sebagian peristiwa melewati batas bayangan dan sebagian lainnya tidak. Jika peristiwa melintasi batas, target peristiwa akan disesuaikan untuk mempertahankan enkapsulasi yang diberikan oleh batas atas root bayangan. Artinya, peristiwa ditargetkan ulang agar seolah-olah berasal dari elemen host, bukan dari elemen internal ke Shadow DOM.
Putar Tindakan 1
- Pertanyaan ini menarik. Anda akan melihat
mouseout
dari elemen host (<div data-host>
) ke node biru. Meskipun berupa node terdistribusi, node tersebut masih berada di host, bukan ShadowDOM. Mengarahkan kursor ke bawah hingga kuning lagi akan menyebabkanmouseout
pada node biru.
Putar Tindakan 2
- Ada satu
mouseout
yang muncul di host (di bagian paling akhir). Biasanya Anda akan melihat peristiwamouseout
dipicu untuk semua blok kuning. Namun, dalam hal ini, elemen-elemen ini bersifat internal untuk Shadow DOM dan peristiwa tidak menggelembung melalui batas atasnya.
Putar Tindakan 3
- Perhatikan bahwa saat Anda mengklik input,
focusin
tidak akan muncul di input, tetapi di node host itu sendiri. Target telah ditargetkan ulang!
Peristiwa yang selalu dihentikan
Peristiwa berikut tidak pernah melewati batas bayangan:
- batal
- error
- pilih
- ubah
- load
- reset
- resize
- scroll
- pilih mulai
Kesimpulan
Semoga Anda setuju bahwa Shadow DOM sangat canggih. Untuk pertama kalinya, kita memiliki enkapsulasi yang tepat tanpa bagasi tambahan <iframe>
atau teknik lama lainnya.
Shadow DOM tentu saja merupakan binatang buas yang rumit, namun menjadi binatang buas yang layak ditambahkan ke platform web. Luangkan waktu untuk mempelajarinya. Pelajari. Mengajukan pertanyaan.
Jika Anda ingin mempelajari lebih lanjut, lihat artikel pengantar Dominic Shadow DOM 101 dan artikel Shadow DOM 201: CSS & Penataan gaya saya.