Konsep lanjutan &DOM API
Artikel ini membahas lebih lanjut hal-hal luar biasa yang dapat Anda lakukan dengan Shadow DOM. Bagian ini dibuat berdasarkan konsep yang dibahas dalam Shadow DOM 101 dan Shadow DOM 201.
Menggunakan beberapa akar bayangan
Jika Anda mengadakan pesta, suasana akan terasa pengap jika semua orang berdesakan di ruangan yang sama. Anda menginginkan opsi untuk mendistribusikan sekelompok orang di beberapa ruangan. Elemen yang menghosting Shadow DOM juga dapat melakukan ini, artinya, elemen tersebut dapat menghosting lebih dari satu shadow root dalam satu waktu.
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", meskipun kita telah melampirkan hierarki bayangan. Hal ini karena pohon bayangan terakhir yang ditambahkan ke host akan menang. Ini adalah stack LIFO sejauh 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>
) karena merupakan placeholder. Namun, bukan sebagai placeholder untuk konten host, node ini adalah host untuk shadow tree lainnya.
Ini adalah Shadow DOM Inception!
Seperti yang mungkin bisa Anda bayangkan, segalanya menjadi semakin rumit ketika Anda semakin jauh
menyelidiki lubang kelinci. Karena alasan ini, spesifikasi sangat jelas tentang apa yang terjadi saat
beberapa elemen <shadow>
sedang diputar:
Melihat kembali contoh awal kita, root1
bayangan pertama dihapus dari
daftar undangan. Menambahkan titik penyisipan <shadow>
akan menampilkannya kembali:
<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". Hal ini karena tempat kita menempatkan
titik penyisipan
<shadow>
. Jika Anda ingin 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 proses rendering.
Apa yang dirender di <shadow>
?
Terkadang ada gunanya mengetahui hierarki 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 root bayangan termuda
menggunakan .shadowRoot
:
var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null
Jika Anda khawatir orang lain melintasi bayangan Anda, tentukan ulang
.shadowRoot
menjadi null:
Object.defineProperty(host, 'shadowRoot', {
get: function() { return null; },
set: function(value) { }
});
Agak aneh, tetapi berhasil. Pada akhirnya, penting untuk diingat bahwa meskipun sangat fantastis, Shadow DOM belum dirancang untuk menjadi fitur keamanan. Jangan mengandalkannya untuk menyelesaikan isolasi konten.
Mem-build Shadow DOM di JS
Jika Anda lebih suka mem-build DOM di JS, HTMLContentElement
dan HTMLShadowElement
memiliki antarmuka untuk itu.
<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 sama 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 "didistribusikan" ke hierarki bayangan disebut…drumroll…node terdistribusi. Elemen tersebut diizinkan untuk melintasi batas bayangan saat titik penyisipan mengundangnya masuk.
Yang aneh secara konseptual tentang titik penyisipan adalah titik tersebut tidak secara fisik
memindahkan DOM. Node host tetap utuh. Titik penyisipan hanya memproyeksikan ulang node
dari host ke dalam hierarki 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 dari shadow DOM. Ini mengarah ke bit tid lainnya:
Element.getDistributedNodes()
Kita tidak dapat menjelajahi <content>
, tetapi .getDistributedNodes()
API
memungkinkan kita membuat kueri node terdistribusi di 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()
-nya:
<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 disebut Shadow DOM itu sulit. Saya ingat saat pertama kali mencoba memahaminya.
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 bereksperimen untuk melihat cara kerja dan titik penyisipan swizzle node host ke dalam hierarki bayangan.
Cobalah dan beri tahu kami pendapat Anda.
Model Peristiwa
Beberapa peristiwa melintasi batas bayangan dan beberapa tidak. Jika peristiwa melintasi batas, target peristiwa akan disesuaikan untuk mempertahankan enkapsulasi yang disediakan oleh batas atas root bayangan. Artinya, peristiwa ditargetkan ulang agar terlihat seperti berasal dari elemen host, bukan elemen internal ke Shadow DOM.
Play Action 1
- Ini menarik. Anda akan melihat
mouseout
dari elemen host (<div data-host>
) ke node biru. Meskipun merupakan node terdistribusi, node masih berada di host, bukan ShadowDOM. Menggerakkan kursor lebih jauh ke bawah ke warna kuning lagi akan menyebabkanmouseout
pada node biru.
Putar Action 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 ini bersifat internal untuk Shadow DOM dan peristiwa tidak akan muncul melalui batas atasnya.
Putar Tindakan 3
- Perhatikan bahwa saat Anda mengklik input,
focusin
tidak muncul di input, tetapi di node host itu sendiri. Penargetan ulang telah dilakukan.
Peristiwa yang selalu dihentikan
Peristiwa berikut tidak pernah melewati batas bayangan:
- batal
- error
- pilih
- ubah
- load
- reset
- resize
- scroll
- selectstart
Kesimpulan
Saya harap Anda akan setuju bahwa Shadow DOM sangat canggih. Untuk pertama kalinya, kita memiliki enkapsulasi yang tepat tanpa beban tambahan <iframe>
atau teknik lama lainnya.
Shadow DOM tentu saja merupakan hewan buas yang rumit, tetapi merupakan hewan buas yang layak ditambahkan ke platform web. Luangkan waktu untuk mempelajarinya. Pelajari. Mengajukan pertanyaan.
Jika ingin mempelajari lebih lanjut, lihat artikel pengantar Dominic Shadow DOM 101 dan artikel Shadow DOM 201: CSS & Penataan Gaya saya.