Saya harus berterima kasih kepada Jeremy Keith dan artikel luar biasa yang dia tulis akhir tahun lalu yang memperkenalkan saya pada konsep komponen web HTML. Inilah saat saya menyadarinya:
Saat Anda menggabungkan beberapa tag yang ada dalam elemen khusus dan kemudian menerapkan perilaku baru menggunakan JavaScript, secara teknis Anda tidak melakukan apa pun yang tidak dapat Anda lakukan sebelumnya dengan beberapa traversal DOM dan penanganan peristiwa. Namun melakukannya dengan komponen web tidak terlalu rapuh. Ini portabel. Ini mengikuti prinsip tanggung jawab individu. Ia hanya melakukan satu hal tetapi melakukannya dengan baik.
Sampai saat itu, saya memiliki asumsi yang salah bahwa… <>setiap orang> Komponen web sepenuhnya mengandalkan kehadiran JavaScript yang dikombinasikan dengan Shadow DOM yang agak menakutkan. Meskipun mungkin untuk membuat komponen web dengan cara ini, ada cara lain. Mungkin cara yang lebih baik? Terutama jika, seperti saya, Anda adalah pendukung perbaikan bertahap. Komponen web HTML pada akhirnya hanyalah HTML.
Meskipun hal ini berada di luar cakupan yang kita bahas di sini, Andy Bell baru-baru ini menerbitkan sebuah artikel yang menawarkan perspektifnya (yang sangat bagus) tentang apa arti peningkatan bertahap.
-Advertisement-.
Mari kita lihat tiga contoh spesifik yang mengilustrasikan apa yang saya yakini sebagai keunggulan utama komponen web HTML — enkapsulasi gaya CSS dan peluang untuk pengoptimalan tambahan — tanpa harus bergantung pada JavaScript untuk bekerja langsung. Kami pasti akan menggunakan JavaScript, tetapi komponennya harus berfungsi tanpanya.
Semua contoh dapat ditemukan di pustaka komponen Boilerplate UI Web saya (dibuat menggunakan Buku Cerita), bersama dengan kode sumber tertaut di GitHub.
Contoh 1: <webui-disclosure>

Saya sangat menyukai cara Chris Ferdinandi mengajarkan cara membuat komponen web dari awal, dengan menggunakan pola pengungkapan (tampilkan/sembunyikan) sebagai contoh. Contoh pertama ini memperluas demonstrasinya.
Mari kita mulai dengan warga negara kelas satu, HTML. Komponen web memungkinkan kita membuat elemen khusus dengan label kita sendiri, seperti yang terjadi pada contoh ini <webui-disclosure>
Tag yang kami gunakan untuk menahan <button>
Dirancang untuk menampilkan/menyembunyikan blok teks <div>
Yang membawa <p>
Dari teks yang ingin kita tampilkan dan sembunyikan.
<webui-disclosure
data-bind-escape-key
data-bind-click-outside
>
<button
type="button"
class="button button--text"
data-trigger
hidden
>
Show / Hide
</button>
<div data-content>
<p>Content to be shown/hidden.</p>
</div>
</webui-disclosure>
Jika JavaScript dinonaktifkan atau tidak dijalankan (karena sejumlah kemungkinan alasan), tombol akan disembunyikan secara default — terima kasih hidden
Tema di dalamnya – dan konten di dalam div hanya ditampilkan secara default.
Cantik. Ini adalah contoh sederhana dari tindakan peningkatan bertahap. Pengunjung dapat melihat konten dengan atau tanpa <button>
.
Saya menyebutkan bahwa contoh ini berasal dari demo awal Chris Ferdinandi. Perbedaan utamanya adalah Anda dapat menutup item dengan mengklik tombol keyboard ESC
Kunci atau klik di mana saja di luar item. Inilah yang dilakukan keduanya [data-attribute]
S aktif <webui-disclosure
Tag ini ditujukan untuk.
Kita mulai dengan mendefinisikan elemen khusus sehingga browser mengetahui apa yang harus dilakukan dengan nama tag yang kita buat:
customElements.define('webui-disclosure', WebUIDisclosure);
Item khusus harus diberi label dengan tag identifikasi putus-putus, mis <my-pizza>
Atau apa pun, tetapi seperti yang dicatat oleh Jim Nielsen, melalui Scott Gill, itu tidak berarti polisi <>Dia punya> Untuk berpindah di antara dua kata.
Saya biasanya lebih suka menggunakan TypeScript untuk menulis JavaScript guna membantu menghilangkan kesalahan bodoh dan memaksakan pemrograman “defensif” pada tingkat tertentu. Namun untuk mempermudah, struktur modul ES komponen web terlihat seperti ini dalam JavaScript biasa:
default class WebUIDisclosure extends HTMLElement {
constructor() {
super();
this.trigger = this.querySelector('[data-trigger]');
this.content = this.querySelector('[data-content]');
this.bindEscapeKey = this.hasAttribute('data-bind-escape-key');
this.bindClickOutside = this.hasAttribute('data-bind-click-outside');
if (!this.trigger || !this.content) return;
this.setupA11y();
this.trigger?.addEventListener('click', this);
}
setupA11y() {
// Add ARIA props/state to button.
}
// Handle constructor() event listeners.
handleEvent(e) {
// 1. Toggle visibility of content.
// 2. Toggle ARIA expanded state on button.
}
// Handle event listeners which are not part of this Web Component.
connectedCallback() {
document.addEventListener('keyup', (e) => {
// Handle ESC key.
});
document.addEventListener('click', (e) => {
// Handle clicking outside.
});
}
disconnectedCallback() {
// Remove event listeners.
}
}
Ingin tahu siapa pendengar acara ini? Pendengar pertama didefinisikan dalam constructor()
berfungsi, sementara sisanya masuk connectedCallback()
Hawke Tesserest menjelaskan alasan postingan ini dengan lebih fasih daripada yang saya bisa.
JavaScript ini tidak diperlukan agar komponen web dapat “berfungsi”, namun ia menambahkan beberapa fungsi yang bagus, belum lagi pertimbangan aksesibilitas, untuk membantu peningkatan bertahap yang memungkinkan <button>
Untuk menampilkan dan menyembunyikan konten. Misalnya, JavaScript memasukkan konten yang sesuai aria-expanded
Dan aria-controls
Atribut yang memungkinkan mereka yang mengandalkan pembaca layar memahami tujuan tombol.
Ini adalah bagian peningkatan progresif untuk contoh ini.
Untuk mempermudah, saya belum menulis CSS tambahan untuk komponen ini. Desain yang Anda lihat hanyalah warisan dari cakupan global atau gaya komponen yang ada (misalnya tipografi dan tombol).
Namun contoh berikut <>Dia melakukannya> Anda memiliki beberapa cakupan CSS tambahan.
Contoh 2: <webui-tabs>
Contoh pertama ini menunjukkan manfaat peningkatan komponen web HTML secara progresif. Manfaat lain yang kami dapatkan adalah gaya CSS <>dikemas,> Ini merupakan cara yang bagus untuk mengatakan bahwa CSS tidak bocor dari komponen. Gaya hanya dicakup pada komponen web dan gaya ini tidak akan bertentangan dengan gaya lain yang diterapkan pada halaman saat ini.
Mari beralih ke contoh kedua, kali ini menunjukkan kekuatan enkapsulasi gaya untuk komponen web <>Dan> Cara mendukung peningkatan bertahap dalam pengalaman pengguna. Kami akan menggunakan komponen bertab untuk mengatur konten ke dalam “panel” yang terlihat ketika Anda mengklik tab yang sesuai dengan panel – hal yang sama yang akan Anda temukan di banyak perpustakaan komponen.

Dimulai dengan struktur HTML:
<webui-tabs>
<div data-tablist>
<a href="#tab1" data-tab>Tab 1</a>
<a href="#tab2" data-tab>Tab 2</a>
<a href="#tab3" data-tab>Tab 3</a>
</div>
<div id="tab1" data-tabpanel>
<p>1 - Lorem ipsum dolor sit amet consectetur.</p>
</div>
<div id="tab2" data-tabpanel>
<p>2 - Lorem ipsum dolor sit amet consectetur.</p>
</div>
<div id="tab3" data-tabpanel>
<p>3 - Lorem ipsum dolor sit amet consectetur.</p>
</div>
</webui-tabs>
Anda mendapatkan idenya: tiga tautan dirancang sebagai tab, dan ketika diklik, tautan tersebut membuka panel tab dengan konten. Perhatikan bahwa setiap [data-tab]
Di daftar tab, targetkan tautan jangkar yang cocok dengan ID panel tab, misalnya, #tab1
, #tab2
dll.
Pertama-tama kita akan melihat enkapsulasi pola karena kita tidak membahasnya pada contoh terakhir. Katakanlah CSS terstruktur seperti ini:
webui-tabs {
[data-tablist] {
/* Default styles without JavaScript */
}
[data-tab] {
/* Default styles without JavaScript */
}
[role="tablist"] {
/* Style role added by JavaScript */
}
[role="tab"] {
/* Style role added by JavaScript */
}
[role="tabpanel"] {
/* Style role added by JavaScript */
}
}
Lihat apa yang terjadi di sini? Kami memiliki dua aturan gaya — [data-tablist]
Dan [data-tab]
— yang berisi gaya default komponen web. Dengan kata lain, pola-pola ini ada terlepas dari apakah JavaScript dimuat atau tidak. Sementara itu, tiga aturan gaya lainnya merupakan penentu yang dimasukkan ke dalam komponen selama JavaScript diaktifkan dan didukung. dengan cara itu, Tiga aturan gaya terakhir hanya berlaku jika JavaScript dihilangkan **role**
atribut pada elemen-elemen tersebut dalam HTML. Di sana, kami sebenarnya memberikan sentuhan optimasi tambahan dengan mengatur gaya hanya ketika JavaScript diperlukan.
Semua gaya ini sepenuhnya dienkapsulasi atau dicakup <webui-tabs>
Tidak ada “kebocoran” yang akan bocor ke gaya komponen web lain, atau bahkan ke hal lain di halaman dalam lingkup global. Kita bahkan dapat memilih untuk mengabaikan nama kelas, penyeleksi kompleks, dan metodologi seperti BEM dan memilih penyeleksi sederhana untuk turunan komponen, sehingga memungkinkan kita menulis pola secara lebih deklaratif pada elemen semantik.
Dengan cepat: DOM “ringan” vs. DOM bayangan
Untuk sebagian besar proyek web, saya biasanya lebih suka mengkompilasi CSS (termasuk bagian Sass dari komponen web) ke dalam satu file CSS sehingga gaya default komponen tersedia dalam lingkup global, meskipun tidak ada JavaScript yang diterapkan.
Namun, dimungkinkan untuk mengimpor style sheet melalui JavaScript Itu hanya dikonsumsi oleh komponen web ini Jika JavaScript tersedia:
import styles from './styles.css';
class WebUITabs extends HTMLElement {
constructor() {
super();
this.adoptedStyleSheets = [styles];
}
}
customElements.define('webui-tabs', WebUITabs);
Alternatifnya, kita bisa menyuntik <style>
Tag yang berisi gaya komponen:
class WebUITabs extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' }); // Required for JavaScript access
this.shadowRoot.innerHTML = `
<style> <!-- styles go here --> </style>
// etc.
`;
}
}
customElements.define('webui-tabs', WebUITabs);
Apapun metode yang Anda pilih, gaya ini diterapkan langsung ke komponen web, yang mencegah kebocoran gaya komponen, namun memungkinkan gaya global untuk diwarisi.
Sekarang pikirkan contoh sederhana ini. Segala sesuatu yang kita tulis di antara tag pembuka dan penutup suatu komponen dianggap sebagai bagian dari DOM “Ringan”.
<my-web-component>
<!-- This is Light DOM -->
<div>
<p>Some content... styles are inherited from the global scope</p>
</div>
----------- Shadow DOM Boundary -------------
| <!-- Anything injected by JavaScript --> |
---------------------------------------------
</my-web-component>
Dave Rupert memiliki artikel bagus yang membuatnya sangat mudah untuk melihat bagaimana gaya eksternal dapat “meretas” Shadow DOM dan memilih elemen di Light DOM. Perhatikan caranya <button>
Elemen yang ditulis di antara tag elemen khusus menerima button
Gaya pemilih dalam CSS global, sedangkan <button>
Data yang dimasukkan melalui JavaScript tidak tersentuh.
Jika kita ingin mendesain Shadow DOM <button>
Kita harus melakukan ini menggunakan gaya internal seperti contoh di atas untuk mengimpor lembar gaya atau memasukkan gaya sebaris <style>
penghalang jalan.
Bukan berarti demikian <>setiap orang> Properti gaya CSS diblokir oleh Shadow DOM. Faktanya, Dave mendefinisikan 37 properti yang dapat digunakan komponen web inherit
sebagian besar mirip dengan format teks, daftar, dan tabel.
Meningkatkan secara progresif komponen tab menggunakan JavaScript
Meskipun contoh kedua ini lebih banyak tentang enkapsulasi pola, ini masih merupakan kesempatan bagus untuk melihat peningkatan bertahap yang kita dapatkan secara gratis dari komponen web. Mari beralih ke JavaScript sekarang sehingga kita dapat melihat bagaimana kita dapat mendukung pengoptimalan tambahan. Kode lengkapnya cukup panjang, jadi saya telah mempersingkatnya sedikit untuk membantu memperjelas poinnya.
default class WebUITabs extends HTMLElement {
constructor() {
super();
this.tablist = this.querySelector('[data-tablist]');
this.tabpanels = this.querySelectorAll('[data-tabpanel]');
this.tabTriggers = this.querySelectorAll('[data-tab]');
if (
!this.tablist ||
this.tabpanels.length === 0 ||
this.tabTriggers.length === 0
) return;
this.createTabs();
this.tabTriggers.forEach((tabTrigger, index) => {
tabTrigger.addEventListener('click', (e) => {
this.bindClickEvent(e);
});
tabTrigger.addEventListener('keydown', (e) => {
this.bindKeyboardEvent(e, index);
});
});
}
createTabs() {
// 1. Hide all tabpanels initially.
// 2. Add ARIA props/state to tabs & tabpanels.
}
bindClickEvent(e) {
e.preventDefault();
// Show clicked tab and update ARIA props/state.
}
bindKeyboardEvent(e, index) {
e.preventDefault();
// Handle keyboard ARROW/HOME/END keys.
}
}
customElements.define('webui-tabs', WebUITabs);
JavaScript memasukkan peran, status, dan properti ARIA ke dalam tab dan blok konten untuk pengguna pembaca layar, serta pengikatan keyboard tambahan sehingga kita dapat bernavigasi antar tab menggunakan keyboard; Misalnya, TAB
Kuncinya dicadangkan untuk mengakses tab aktif komponen dan konten apa pun yang dapat difokuskan dalam tab aktif tabpanel
Anda dapat berpindah antar tab menggunakan… ARROW
Jadi, jika JavaScript gagal dimuat, pengalaman default tetap merupakan pengalaman yang dapat diakses dengan tab tetap tertaut ke panelnya sendiri, dan panel tersebut ditumpuk secara alami secara vertikal, satu di atas yang lain.
Jika JavaScript diaktifkan dan didukung, kami akan mendapatkan pengalaman yang lebih baik, dengan mempertimbangkan pertimbangan aksesibilitas yang diperbarui.
Contoh 3: <webui-ajax-loader>

Contoh terakhir ini berbeda dari dua contoh sebelumnya dalam hal… Itu seluruhnya dibuat oleh JavaScriptDan Ini menggunakan Bayangan DOM<>.> Hal ini karena ini hanya digunakan untuk menunjukkan status “memuat” permintaan Ajax, dan oleh karena itu hanya diperlukan ketika JavaScript diaktifkan.
Tag HTML hanyalah tag komponen pembuka dan penutup:
<webui-ajax-loader></webui-ajax-loader>
Sintaks JavaScript yang disederhanakan:
default class WebUIAjaxLoader extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<svg role="img" part="svg">
<title>loading</title>
<circle cx="50" cy="50" r="47" />
</svg>
`;
}
}
customElements.define('webui-ajax-loader',WebUIAjaxLoader);
Catat langsung dari gerbang itu <>Semuanya> di antara <webui-ajax-loader>
Tag dimasukkan menggunakan JavaScript, yang berarti semuanya berada di Shadow DOM, dikelilingi oleh teks dan gaya lain yang tidak digabungkan secara langsung dengan komponen.
Tapi perhatikan juga part
Atribut yang disetel ke <svg>
Barang. Di sini kita akan memperbesar gambar:
<svg role="img" part="svg">
<!-- etc. -->
</svg>
Ini adalah cara lain untuk mendesain item secara kustom: Bagian yang diberi nama. Sekarang kita dapat mendesain SVG dari <>di luar> Dari templat literal yang kami gunakan untuk membuat item. di sana ::part
Pemilih semu untuk mencapai ini:
webui-ajax-loader::part(svg) {
// Shadow DOM styles for the SVG...
}
Inilah hal kerennya: Pemilih ini dapat mengakses properti CSS khusus, baik yang ditentukan secara global atau lokal untuk elemen tersebut.
webui-ajax-loader {
--fill: orangered;
}
webui-ajax-loader::part(svg) {
fill: var(--fill);
}
Dalam hal optimasi tambahan, JavaScript menyediakan semua yang ditawarkan HTML. Artinya loader hanya ditampilkan jika JavaScript diaktifkan dan didukung. Setelah selesai, SVG ditambahkan, dengan judul yang dapat diakses dan segalanya.
kesimpulan
Itu saja contohnya! Apa yang saya harap adalah Anda kini mendapatkan inspirasi yang sama seperti yang saya rasakan ketika saya membaca postingan Jeremy Keith: Komponen web HTML adalah fitur yang mengutamakan HTML.
Tentu saja, JavaScript memainkan peran besar, tetapi hanya sebatas yang diperlukan. Butuh kemasan lebih banyak? Apakah Anda ingin menambahkan beberapa fitur pengalaman pengguna ketika browser pengunjung Anda mendukungnya? Inilah tujuan JavaScript dan apa yang menjadikan komponen web HTML sebagai tambahan yang bagus untuk platform web – komponen tersebut mengandalkan bahasa web yang mendasarinya untuk melakukan apa yang dirancang sejak awal, tanpa terlalu bergantung pada satu bahasa atau lainnya. .
Komponen web HTML membuat pengoptimalan progresif dan pembungkusan CSS menjadi lebih mudah! Artikel ini awalnya diterbitkan di CSS-Tricks, bagian dari keluarga DigitalOcean. Anda harus mendapatkan buletin.