Shadow DOM 201

CSS dan gaya

Artikel ini membahas lebih lanjut hal-hal luar biasa yang dapat Anda lakukan dengan Shadow DOM. Cara ini dibuat berdasarkan konsep yang dibahas dalam Shadow DOM 101. Jika Anda mencari pengantar, lihat artikel tersebut.

Pengantar

Mari kita hadapi. Tidak ada yang menarik dari markup tanpa gaya. Untungnya, orang-orang hebat di balik Web Components telah mengantisipasi hal ini dan tidak membiarkan kita menunggu. Modul Cakupan CSS menentukan banyak opsi untuk menata gaya konten dalam hierarki bayangan.

Enkapsulasi gaya

Salah satu fitur inti Shadow DOM adalah batas bayangan. Library ini memiliki banyak properti bagus, tetapi salah satu yang terbaik adalah menyediakan enkapsulasi gaya secara gratis. Dengan kata lain:

<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
  <style>
    h3 {
      color: red;
    }
  </style>
  <h3>Shadow DOM</h3>
`;
</script>

Ada dua pengamatan menarik tentang demo ini:

  • Ada h3 lain di halaman ini, tetapi satu-satunya yang cocok dengan pemilih h3, dan karenanya bergaya merah, adalah yang ada di ShadowRoot. Sekali lagi, gaya cakupan secara default.
  • Aturan gaya lain yang ditentukan di halaman ini yang menargetkan h3 tidak masuk ke konten saya. Hal ini karena pemilih tidak melintasi batas bayangan.

Pesan moralnya? Kita memiliki enkapsulasi gaya dari dunia luar. Terima kasih, Shadow DOM!

Menyesuaikan gaya elemen host

:host memungkinkan Anda memilih dan menata gaya elemen yang menghosting hierarki bayangan:

<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
  <style>
    :host {
      text-transform: uppercase;
    }
  </style>
  <content></content>
`;
</script>

Satu hal yang perlu diperhatikan adalah aturan di halaman induk memiliki spesifitas yang lebih tinggi daripada aturan :host yang ditentukan dalam elemen, tetapi spesifitas yang lebih rendah daripada atribut style yang ditentukan pada elemen host. Hal ini memungkinkan pengguna mengganti gaya visual Anda dari luar. :host juga hanya berfungsi dalam konteks ShadowRoot sehingga Anda tidak dapat menggunakannya di luar Shadow DOM.

Bentuk fungsional :host(<selector>) memungkinkan Anda menargetkan elemen host jika cocok dengan <selector>.

Contoh - hanya cocok jika elemen itu sendiri memiliki class .different (misalnya <x-foo class="different"></x-foo>):

:host(.different) {
    ...
}

Merespons status pengguna

Kasus penggunaan umum untuk :host adalah saat Anda membuat Elemen Kustom dan ingin bereaksi terhadap berbagai status pengguna (:hover, :focus, :active, dll.).

<style>
  :host {
    opacity: 0.4;
    transition: opacity 420ms ease-in-out;
  }
  :host(:hover) {
    opacity: 1;
  }
  :host(:active) {
    position: relative;
    top: 3px;
    left: 3px;
  }
</style>

Menerapkan tema pada elemen

Class pseudo :host-context(<selector>) cocok dengan elemen host jika elemen tersebut atau salah satu ancestor-nya cocok dengan <selector>.

Penggunaan umum :host-context() adalah untuk tema elemen berdasarkan lingkungannya. Misalnya, banyak orang melakukan tema dengan menerapkan class ke <html> atau <body>:

<body class="different">
  <x-foo></x-foo>
</body>

Anda dapat :host-context(.different) untuk menata gaya <x-foo> jika merupakan turunan elemen dengan class .different:

:host-context(.different) {
  color: red;
}

Hal ini memberi Anda kemampuan untuk mengenkapsulasi aturan gaya di DOM Bayangan elemen yang memberi gaya secara unik, berdasarkan konteksnya.

Mendukung beberapa jenis host dari dalam satu root bayangan

Penggunaan lain untuk :host adalah jika Anda membuat library tema dan ingin mendukung gaya banyak jenis elemen host dari dalam Shadow DOM yang sama.

:host(x-foo) {
    /* Applies if the host is a <x-foo> element.*/
}

:host(x-foo:host) {
    /* Same as above. Applies if the host is a <x-foo> element. */
}

:host(div) {
    /* Applies if the host element is a <div>. */
}

Menata gaya internal Shadow DOM dari luar

Pseudo-elemen ::shadow dan kombinator /deep/ seperti memiliki pedang Vorpal dari otoritas CSS. Elemen ini memungkinkan penetrasi melalui batas Shadow DOM untuk menata gaya elemen dalam hierarki bayangan.

Pseudo-elemen ::shadow

Jika elemen memiliki setidaknya satu hierarki bayangan, elemen pseudo ::shadow akan cocok dengan root bayangan itu sendiri. Dengan ini, Anda dapat menulis pemilih yang menata gaya node internal ke shadow dom elemen.

Misalnya, jika elemen menghosting root bayangan, Anda dapat menulis #host::shadow span {} untuk menata gaya semua span dalam hierarki bayangannya.

<style>
  #host::shadow span {
    color: red;
  }
</style>

<div id="host">
  <span>Light DOM</span>
</div>

<script>
  var host = document.querySelector('div');
  var root = host.createShadowRoot();
  root.innerHTML = `
    <span>Shadow DOM</span>
    <content></content>
  `;
</script>

Contoh (elemen kustom) - <x-tabs> memiliki turunan <x-panel> di Shadow DOM-nya. Setiap panel menghosting hierarki bayangan sendiri yang berisi judul h2. Untuk menata gaya judul tersebut dari halaman utama, Anda dapat menulis:

x-tabs::shadow x-panel::shadow h2 {
    ...
}

Kombinator /deep/

Kombinator /deep/ mirip dengan ::shadow, tetapi lebih canggih. Fungsi ini sepenuhnya mengabaikan semua batas bayangan dan melintasi sejumlah hierarki bayangan. Sederhananya, /deep/ memungkinkan Anda melihat bagian dalam elemen dan menargetkan node apa pun.

Kombinator /deep/ sangat berguna di dunia Elemen Khusus, tempat memiliki beberapa tingkat DOM Bayangan adalah hal yang umum. Contoh utamanya adalah menyusun bertingkat sekumpulan elemen kustom (masing-masing menghosting hierarki bayangan sendiri) atau membuat elemen yang diwarisi dari elemen lain menggunakan <shadow>.

Contoh (elemen kustom) - pilih semua elemen <x-panel> yang merupakan turunan dari <x-tabs>, di mana saja dalam hierarki:

x-tabs /deep/ x-panel {
    ...
}

Contoh - menata gaya semua elemen dengan class .library-theme, di mana saja dalam hierarki bayangan:

body /deep/ .library-theme {
    ...
}

Menggunakan querySelector()

Sama seperti .shadowRoot yang membuka hierarki bayangan untuk traversal DOM, pengombinator membuka hierarki bayangan untuk traversal pemilih. Daripada menulis rantai gila bertingkat, Anda dapat menulis satu pernyataan:

// No fun.
document.querySelector('x-tabs').shadowRoot
        .querySelector('x-panel').shadowRoot
        .querySelector('#foo');

// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');

Menata gaya elemen native

Kontrol HTML native merupakan tantangan untuk gaya. Banyak orang menyerah dan membuat sendiri. Namun, dengan ::shadow dan /deep/, elemen apa pun di platform web yang menggunakan Shadow DOM dapat diberi gaya. Contoh yang bagus adalah jenis <input> dan <video>:

video /deep/ input[type="range"] {
  background: hotpink;
}

Membuat hook gaya

Penyesuaian bagus. Dalam kasus tertentu, Anda mungkin ingin membuat lubang di pelindung gaya Shadow dan membuat hook untuk gaya orang lain.

Menggunakan ::shadow dan /deep/

Ada banyak kemampuan di balik /deep/. Hal ini memberi penulis komponen cara untuk menetapkan setiap elemen sebagai gaya atau sekumpulan elemen sebagai tema.

Contoh - menata gaya semua elemen yang memiliki class .library-theme, dengan mengabaikan semua hierarki bayangan:

body /deep/ .library-theme {
    ...
}

Menggunakan elemen pseudo kustom

WebKit dan Firefox menentukan elemen pseudo untuk menata gaya bagian internal elemen browser native. Contoh yang baik adalah input[type=range]. Anda dapat menata gaya thumb penggeser <span style="color:blue">blue</span> dengan menargetkan ::-webkit-slider-thumb:

input[type=range].custom::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: blue;
  width: 10px;
  height: 40px;
}

Serupa dengan cara browser menyediakan hook gaya ke beberapa internal, penulis konten Shadow DOM dapat menetapkan elemen tertentu sebagai gaya yang dapat diubah oleh pihak luar. Hal ini dilakukan melalui elemen pseudo kustom.

Anda dapat menetapkan elemen sebagai elemen pseudo kustom menggunakan atribut pseudo. Nilai, atau namanya, harus diawali dengan "x-". Dengan demikian, akan terbentuk asosiasi dengan elemen tersebut di hierarki bayangan dan memberi orang luar jalur yang ditetapkan untuk melintasi batas bayangan.

Berikut adalah contoh pembuatan widget penggeser kustom dan mengizinkan seseorang untuk menata gaya thumb penggesernya menjadi biru:

<style>
  #host::x-slider-thumb {
    background-color: blue;
  }
</style>
<div id="host"></div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <div>
      <div pseudo="x-slider-thumb"></div>' +
    </div>
  `;
</script>

Menggunakan Variabel CSS

Cara yang efektif untuk membuat hook tema adalah melalui Variabel CSS. Pada dasarnya, membuat "placeholder gaya" untuk diisi oleh pengguna lain.

Bayangkan penulis elemen kustom yang menandai placeholder variabel di Shadow DOM-nya. Satu untuk menata gaya font tombol internal dan satu lagi untuk warnanya:

button {
  color: var(--button-text-color, pink); /* default color will be pink */
  font-family: var(--button-font);
}

Kemudian, penyempan elemen menentukan nilai tersebut sesuai keinginannya. Mungkin untuk mencocokkan tema Comic Sans yang super keren di halaman mereka sendiri:

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

Karena cara Variabel CSS mewarisi, semuanya berjalan lancar dan ini berfungsi dengan baik. Seluruh gambar akan terlihat seperti ini:

<style>
  #host {
    --button-text-color: green;
    --button-font: "Comic Sans MS", "Comic Sans", cursive;
  }
</style>
<div id="host">Host node</div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <style>
      button {
        color: var(--button-text-color, pink);
        font-family: var(--button-font);
      }
    </style>
    <content></content>
  `;
</script>

Mereset gaya

Gaya yang dapat diwariskan seperti font, warna, dan tinggi baris terus memengaruhi elemen di DOM Bayangan. Namun, untuk fleksibilitas maksimum, Shadow DOM memberi kita properti resetStyleInheritance untuk mengontrol apa yang terjadi di batas bayangan. Anggaplah ini sebagai cara untuk memulai dari awal saat membuat komponen baru.

resetStyleInheritance

Berikut adalah demo yang menunjukkan bagaimana hierarki bayangan terpengaruh dengan mengubah resetStyleInheritance:

<div>
  <h3>Light DOM</h3>
</div>

<script>
  var root = document.querySelector('div').createShadowRoot();
  root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
  root.innerHTML = `
    <style>
      h3 {
        color: red;
      }
    </style>
    <h3>Shadow DOM</h3>
    <content select="h3"></content>
  `;
</script>

<div class="demoarea" style="width:225px;">
  <div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
  <button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>

<script>
  var container = document.querySelector('#style-ex-inheritance');
  var root = container.createShadowRoot();
  //root.resetStyleInheritance = false;
  root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';

  document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
    root.resetStyleInheritance = !root.resetStyleInheritance;
    e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
    document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
  });
</script>
Properti yang diwarisi DevTools

Memahami .resetStyleInheritance sedikit lebih rumit, terutama karena hanya memengaruhi properti CSS yang dapat diwariskan. Pesan tersebut menyatakan: saat Anda mencari properti untuk diwarisi, di batas antara halaman dan ShadowRoot, jangan wariskan nilai dari host, tetapi gunakan nilai initial (sesuai spesifikasi CSS).

Jika Anda tidak yakin properti mana yang diwarisi di CSS, lihat daftar praktis ini atau alihkan kotak centang "Tampilkan yang diwarisi" di panel Elemen.

Menata gaya node terdistribusi

Node terdistribusi adalah elemen yang dirender di titik penyisipan (elemen <content>). Elemen <content> memungkinkan Anda memilih node dari Light DOM dan merendernya di lokasi yang telah ditentukan di Shadow DOM. Secara logis, elemen tersebut tidak berada di Shadow DOM; elemen tersebut masih merupakan turunan dari elemen host. Titik penyisipan hanyalah hal rendering.

Node terdistribusi mempertahankan gaya dari dokumen utama. Artinya, aturan gaya dari halaman utama akan terus diterapkan ke elemen, meskipun saat dirender di titik penyisipan. Sekali lagi, node terdistribusi secara logis masih berada di dom light dan tidak bergerak. Objek tersebut hanya dirender di tempat lain. Namun, saat node didistribusikan ke Shadow DOM, node tersebut dapat menggunakan gaya tambahan yang ditentukan di dalam hierarki bayangan.

Elemen pseudo ::content

Node yang didistribusikan adalah turunan dari elemen host, jadi bagaimana kita dapat menargetkan node tersebut dari dalam Shadow DOM? Jawabannya adalah elemen pseudo ::content CSS. Ini adalah cara untuk menargetkan node Light DOM yang melewati titik penyisipan. Contoh:

::content > h3 menata gaya tag h3 yang melewati titik penyisipan.

Mari kita lihat contohnya:

<div>
  <h3>Light DOM</h3>
  <section>
    <div>I'm not underlined</div>
    <p>I'm underlined in Shadow DOM!</p>
  </section>
</div>

<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
  <style>
    h3 { color: red; }
      content[select="h3"]::content > h3 {
      color: green;
    }
    ::content section p {
      text-decoration: underline;
    }
  </style>
  <h3>Shadow DOM</h3>
  <content select="h3"></content>
  <content select="section"></content>
`;
</script>

Mereset gaya di titik penyisipan

Saat membuat ShadowRoot, Anda memiliki opsi untuk mereset gaya yang diwarisi. Titik penyisipan <content> dan <shadow> juga memiliki opsi ini. Saat menggunakan elemen ini, tetapkan .resetStyleInheritance di JS atau gunakan atribut reset-style-inheritance boolean pada elemen itu sendiri.

  • Untuk titik penyisipan ShadowRoot atau <shadow>: reset-style-inheritance berarti properti CSS yang dapat diwarisi ditetapkan ke initial di host, sebelum mencapai konten bayangan Anda. Lokasi ini dikenal sebagai batas atas.

  • Untuk titik penyisipan <content>: reset-style-inheritance berarti properti CSS yang dapat diwariskan ditetapkan ke initial sebelum turunan host didistribusikan di titik penyisipan. Lokasi ini dikenal sebagai batas bawah.

Kesimpulan

Sebagai penulis elemen kustom, kita memiliki banyak opsi untuk mengontrol tampilan dan nuansa konten. Shadow DOM membentuk dasar untuk dunia baru yang berani ini.

DOM Bayangan memberi kita enkapsulasi gaya tercakup dan cara untuk memasukkan sebanyak (atau sesedikit) dunia luar yang kita pilih. Dengan menentukan elemen pseudo kustom atau menyertakan placeholder Variabel CSS, penulis dapat memberikan hook gaya yang praktis bagi pihak ketiga untuk menyesuaikan konten mereka lebih lanjut. Secara keseluruhan, penulis web memiliki kontrol penuh terhadap cara konten mereka ditampilkan.