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 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:

  1. "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>';.
  2. 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.

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

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.