Shadow DOM 301

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 root bayangan

Jika Anda mengadakan pesta, suasana akan terasa pengap jika semua orang berdesakan di ruangan yang sama. Anda ingin memiliki opsi untuk mendistribusikan grup orang ke beberapa ruangan. Elemen yang menghosting Shadow DOM juga dapat melakukannya, yaitu, elemen tersebut dapat menghosting lebih dari satu root bayangan 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", 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 (<content>) normal 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 dapat Anda bayangkan, semakin dalam Anda menyelidiki, semakin rumit hal-hal yang akan Anda temukan. 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:

  1. "Root 2 FTW" masih dirender di atas "Root 1 FTW". Hal ini karena tempat kita menempatkan titik penyisipan <shadow>. Jika Anda ingin membalik, pindahkan titik penyisipan: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';.
  2. Perhatikan bahwa sekarang ada titik penyisipan <content> di root1. Hal ini membuat node teks "Light DOM" muncul untuk 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

Mendapatkan 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 isolasi konten yang lengkap.

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. Hal ini mengarah pada informasi lain:

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.

Visualizer Shadow DOM
Meluncurkan Visualizer Shadow DOM

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 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 ini masih berada di host, bukan ShadowDOM. Menggerakkan kursor lebih jauh ke bawah ke warna kuning lagi akan menyebabkan mouseout pada node biru.

Putar Action 2

  • Ada satu mouseout yang muncul di host (di bagian paling akhir). Biasanya, Anda akan melihat peristiwa mouseout 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

Semoga Anda 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 Anda ingin mempelajari lebih lanjut, lihat artikel pengantar Dominic Shadow DOM 101 dan artikel saya Shadow DOM 201: CSS & Styling.