Shadow DOM 201

CSS dan gaya visual

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. Jika Anda mencari informasi perkenalan, baca artikel tersebut.

Pengantar

Mari kita hadapi. Tidak ada yang menarik dari markup tanpa gaya. Beruntung bagi kami, orang-orang brilian di balik Komponen Web mengetahui hal ini dan tidak meninggalkan kami. 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 bagus, tetapi salah satu yang terbaik adalah menyediakan enkapsulasi gaya secara gratis. Dinyatakan dengan cara 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 diberi gaya merah, adalah yang ada di ShadowRoot. Sekali lagi, gaya cakupan secara default.
  • Aturan gaya lain yang ditentukan pada halaman ini yang menargetkan h3 tidak disertakan ke dalam konten saya. Hal itu karena pemilih tidak melewati batas bayangan.

Moral dari cerita ini? 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 pohon 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>

Salah satu kesalahannya adalah aturan di halaman induk memiliki kekhususan lebih tinggi daripada aturan :host yang ditentukan dalam elemen, tetapi kekhususannya 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 - cocokkan hanya jika elemen tersebut memiliki class .different (misalnya <x-foo class="different"></x-foo>):

:host(.different) {
    ...
}

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

Memberi tema elemen

Class pseudo :host-context(<selector>) cocok dengan elemen host jika elemen host atau ancestornya cocok dengan <selector>.

Penggunaan umum :host-context() adalah untuk memberi tema elemen berdasarkan lingkungannya. Misalnya, banyak orang menerapkan 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 dari elemen dengan class .different:

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

Hal ini memberi Anda kemampuan untuk mengenkapsulasi aturan gaya dalam Shadow DOM elemen yang menata gayanya secara unik, berdasarkan konteksnya.

Mendukung beberapa jenis host dari dalam satu root bayangan

Penggunaan lain :host adalah jika Anda membuat library tema dan ingin mendukung penataan gaya berbagai 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

Elemen pseudo ::shadow dan kombinator /deep/ seperti memiliki pedang Vorpal dari otoritas CSS. Fungsi ini memungkinkan menembus batas Shadow DOM untuk menata gaya elemen dalam shadow tree.

Elemen semu ::shadow

Jika suatu elemen memiliki setidaknya satu shadow tree, pseudo-elemen ::shadow akan cocok dengan root bayangan itu sendiri. Alat ini memungkinkan Anda menulis pemilih yang menata gaya node internal ke dom bayangan 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> dalam Shadow DOM-nya. Setiap panel menghosting pohon bayangannya sendiri yang berisi judul h2. Untuk menata gaya judul tersebut dari halaman utama, seseorang dapat menulis:

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

Pengombinator /deep/

Kombinator /deep/ mirip dengan ::shadow, tetapi lebih canggih. Fungsi ini sepenuhnya mengabaikan semua batas bayangan dan menyilang ke sejumlah pohon bayangan. Sederhananya, /deep/ memungkinkan Anda menelusuri isi elemen dan menargetkan node mana pun.

Kombinasi /deep/ sangat berguna dalam dunia Elemen Kustom yang memiliki beberapa level Shadow DOM biasanya. Contoh utama adalah menyusun bertingkat banyak elemen kustom (masing-masing menghosting pohon bayangannya sendiri) atau membuat elemen yang mewarisi 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 - tata gaya semua elemen dengan class .library-theme, di mana saja pada pohon bayangan:

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

Bekerja dengan querySelector()

Sama seperti .shadowRoot yang membuka shadow tree untuk traversal DOM, kombinator membuka shadow tree untuk traversal pemilih. Alih-alih menulis rantai kegilaan bersarang, 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 adalah tantangan untuk menata gaya. Banyak orang menyerah dan menyerah. Namun, dengan ::shadow dan /deep/, setiap elemen 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 itu bagus. Dalam kasus tertentu, Anda mungkin perlu membuat lubang di perisai gaya Shadow dan membuat hook agar orang lain dapat menata gaya.

Menggunakan ::shadow dan /deep/

Ada banyak kekuatan di balik /deep/. Ini memberi penulis komponen cara untuk menetapkan elemen individual sebagai elemen yang dapat ditata gayanya atau banyak elemen sebagai tema.

Contoh - tata gaya semua elemen yang memiliki class .library-theme, dengan mengabaikan semua shadow tree:

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 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 penataan gaya ke dalam beberapa internal, penulis konten Shadow DOM dapat menetapkan elemen tertentu sebagai dapat ditata gayanya oleh orang luar. Tindakan ini dilakukan melalui elemen pseudo kustom.

Anda dapat menetapkan elemen sebagai elemen pseudo kustom menggunakan atribut pseudo. Nilai atau namanya harus diawali dengan "x-". Cara ini akan menghasilkan hubungan dengan elemen tersebut dalam shadow tree dan memberikan jalur khusus kepada orang luar untuk melintasi batas bayangan.

Berikut ini contoh pembuatan widget penggeser kustom dan mengizinkan seseorang untuk menata gaya penggeser dengan ibu jari berwarna 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 ampuh untuk membuat hook tema adalah melalui Variabel CSS. Pada dasarnya, membuat "placeholder gaya" untuk diisi pengguna lain.

Bayangkan penulis elemen kustom yang menandai placeholder variabel dalam Shadow DOM mereka. 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, penyemat elemen mendefinisikan nilai-nilai tersebut sesuai keinginan mereka. Mungkin untuk mencocokkan tema Comic Sans yang sangat keren di laman mereka sendiri:

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

Karena cara Variabel CSS mewarisi, semuanya berwarna oranye dan berfungsi dengan baik. Seluruh gambarnya 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 tidak dapat diwariskan seperti font, warna, dan tinggi garis akan tetap memengaruhi elemen di Shadow DOM. Namun, untuk fleksibilitas maksimum, Shadow DOM memberi kita properti resetStyleInheritance untuk mengontrol apa yang terjadi pada batas bayangan. Anggap saja ini sebagai cara untuk memulai dari awal saat membuat komponen baru.

resetStyleInheritance

Di bawah ini adalah demo yang menunjukkan bagaimana pohon bayangan dipengaruhi oleh perubahan 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 ini menjelaskan: saat Anda mencari properti untuk diwarisi, pada batas antara halaman dan ShadowRoot, jangan mewarisi nilai dari host, tetapi gunakan nilai initial sebagai gantinya (sesuai spesifikasi CSS).

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

Menata gaya node terdistribusi

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

{i>Node<i} terdistribusi mempertahankan gaya dari dokumen utama. Artinya, aturan gaya dari halaman utama akan tetap berlaku untuk elemen, bahkan saat aturan tersebut dirender pada titik penyisipan. Sekali lagi, simpul terdistribusi masih secara logis berada dalam dom terang dan tidak bergerak. File tersebut hanya dirender di tempat lain. Namun, setelah didistribusikan ke dalam Shadow DOM, node dapat mengambil gaya tambahan yang ditentukan di dalam shadow tree.

::content elemen pseudo

Node terdistribusi adalah turunan dari elemen host, jadi bagaimana cara menargetkannya 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 menyetel ulang gaya yang diwarisi. Titik penyisipan <content> dan <shadow> juga memiliki opsi ini. Saat menggunakan elemen ini, tetapkan .resetStyleInheritance di JS atau gunakan atribut boolean reset-style-inheritance pada elemen itu sendiri.

  • Untuk titik penyisipan ShadowRoot atau <shadow>: reset-style-inheritance berarti properti CSS yang dapat diwariskan disetel 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 menantang ini.

Shadow DOM memberi kita enkapsulasi gaya cakupan dan cara untuk mengizinkan sebanyak (atau sesedikit mungkin) dunia luar yang kita pilih. Dengan menentukan elemen pseudo kustom atau menyertakan placeholder Variabel CSS, penulis dapat memberikan hook penataan gaya yang mudah kepada pihak ketiga untuk menyesuaikan kontennya lebih lanjut. Secara keseluruhan, penulis web memegang kendali penuh atas bagaimana konten mereka direpresentasikan.