Shadow DOM 301

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:

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

Visualizer Shadow DOM
Luncurkan Shadow DOM Visualizer

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 menyebabkan mouseout pada node biru.

Putar Tindakan 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-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.