Shadow DOM 201

CSS dan gaya

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. Jika Anda mencari pengantar, lihat artikel tersebut.

Pengantar

Mari kita hadapi. Tidak ada yang seksi tentang 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. Class ini memiliki banyak properti yang 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!

Menata 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 kekhususan yang lebih tinggi daripada aturan :host yang ditentukan dalam elemen, tetapi memiliki kekhususan 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>

Penerapan 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 membuat 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 Shadow DOM elemen yang menata gayanya secara unik, berdasarkan konteksnya.

Mendukung beberapa jenis host dari dalam satu shadow root

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 DOM Bayangan dari luar

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

Pseudo-elemen ::shadow

Jika sebuah elemen memiliki setidaknya satu pohon bayangan, elemen semu ::shadow akan cocok dengan root bayangan itu sendiri. Hal ini memungkinkan Anda 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 pohon bayangannya 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. Fitur ini sepenuhnya mengabaikan semua batas bayangan dan menyeberang ke sejumlah pohon 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 Shadow DOM 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 pohon bayangan untuk traversal DOM, pengombinator membuka pohon 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 yang 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 perisai penataan gaya Shadow dan membuat hook untuk ditata oleh 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 bagus adalah input[type=range]. Anda dapat menata gaya ibu jari 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 dapat diberi gaya 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 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 efektif untuk membuat hook tema adalah melalui Variabel CSS. Pada dasarnya, membuat "placeholder gaya" untuk diisi oleh pengguna lain.

Bayangkan seorang penulis elemen kustom yang menandai placeholder variabel dalam 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 Shadow DOM. 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. Tertulis: saat Anda mencari properti untuk diwarisi, pada batas antara halaman dan ShadowRoot, jangan warisi nilai dari host, tetapi gunakan nilai initial (sesuai spesifikasi CSS).

Jika Anda tidak yakin properti mana yang diwarisi dalam CSS, lihat daftar praktis ini atau pilih kotak centang "Tampilkan warisan" 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, {i>node<i} terdistribusi masih secara logis berada di {i>light dom<i} dan tidak bergerak. Mereka hanya dirender di tempat lain. Namun, saat didistribusikan ke Shadow DOM, node dapat menggunakan gaya tambahan yang ditentukan di dalam hierarki shadow.

Elemen pseudo ::content

Node terdistribusi 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 pada 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 diwarisi 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.

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