Когда я приходил на конференцию и видел презентации на тему веб-компонентов, я всегда думал, что это довольно изящно, но с другой стороны добавляет сложности. Тысяча строк JavaScript, чтобы сохранить всего 4 строки HTML. Выступающий или неизбежно скрывал огромное количество JS кода, или погружался в сложные детали, и мои глаза тускнели, когда я думал о том, покрывают ли мои суточные выплаты закуски.
Однако в недавнем проекте, который создан для
более легкого изучения HTML (Конечно, путем добавления зомби и
глупых шуток), я решил, что необходимо описать каждый элемент HTML
в спецификации. Не считая той конференции, я впервые начинал
знакомство с <slot>
и
<template>
элементами, и когда я захотел
написать что-то интересное, мне пришлось углубиться в тему.
И в процессе углубления я понял: веб-компоненты проще, чем я думал.
Либо веб-компоненты прошли долгий путь развития с тех пор, как я поймал себя, мечтающим о закусках на конференции, либо позволил моему изначальному страху помешать по-настоящему узнать их, а возможно и то, и другое.
Я здесь, чтобы сказать вам: да, вы можете создать веб-компонент. Давайте оставим все отвлекающие факторы, страхи и даже закуски за дверью, чтобы сделать все вместе.
Начнем с <template>
<template>
- это HTML элемент, позволяющий
создать нам шаблон (HTML структуру для веб-компонентов).
<template> <p>The Zombies are coming!</p></template>
Элемент <template>
- очень важный, потому что
позволяет держать все вместе. Это как база для вашего дома, база, с
которой начинает строиться все, что мы называем готовым зданием.
Давайте использовать этот небольшой фрагмент кода для нашего
<apocalyptic-warning>
компонента, который
оповещает нас о наступлении зомби-апокалипсиса.
Тогда есть компонент <slot>
<slot>
- это всего лишь другой HTML элемент,
как и <template>
. Но в нашем случае
<slot>
настраивает то, что
<template>
отображает на странице.
<template> <p>The <slot>Zombies</slot> are coming!</p></template>
Здесь мы добавили слот с словом "Zombies" (Слово ли это?) в
разметку <template>
. Если мы ничего не делаем со
слотом, по умолчанию он отображает контент между тегами. В нашем
случае это будет "Zombies".
Использование <slot>
похоже на
placeholder
. Мы можем использовать
placeholder
, тогда будет отображаться текст по
умолчанию, или указать что-то другое с помощью атрибута
name
.
<template> <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>
name
атрибут сообщает веб-компоненту о том, какое
содержание должно быть отображено в <template>
.
Прямо сейчас у нас есть "whats-coming" слот. Мы предполагаем, что
зомби придут первыми при наступлении зомби-апокалипсиса, но
<slot>
дает нам гибкость, чтобы вставить что-то
еще, если окажется, что первыми придут роботы или оборотни.
Использование веб-компонента
Технически, мы закончили писать компонент и уже можем вставить в любое место, где захотим.
Код
<apocalyptic-warning> <span slot="whats-coming">Halitosis Laden Undead Minions</span></apocalyptic-warning><template> <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>
Видите, что мы делаем? Мы помещаем
<apocalyptic-warning>
компонент на страницу так
же, как и любой другой HTML элемент. Однако мы добавили
<span>
в слот "whats-coming".
<span>
и его содержимое будет отображено на
месте "Zombies", когда компонент отобразится.
Стоит обратить внимание, что в названиях веб-компонентов должен быть дефис, чтобы предотвратить конфликт с HTML элементами, которые могут быть добавлены позже. Это то, о чем вы должны знать, когда дело касается веб-компонентов.
Все еще со мной? Не так уж и страшно, не правда ли? Что ж, минус
зомби. Нам еще надо будет немного поработать, чтобы сделать замену
<slot>
возможной, именно здесь мы начинаем
Регистрация компонента
Как я уже сказал ранее, вам нужно немного JavaScript кода, чтобы все это работало, но это не очень сложный, многострочный и глубокий код, как я всегда думал. Надеюсь, я смогу убедить вас.
Вам нужна функция-конструктор, которая регистрирует веб-компонент. Иначе наше компоненты будет как нежить: она есть, но не полностью живая.
Вот конструктор, который мы будем использовать.
Код
// Определяем кастомный веб-компонент с подходящим именемcustomElements.define("apocalyptic-warning", class extends HTMLElement { // Наследование обеспечивает, что мы имеет свойства и методы по умолчанию встроенного HTML элемента // Вызывается всякий раз, когда создаеся новый элемент constructor() { // Выываем родительский конструктор, т.е конструктор для HTMLElement. Таким образом устанавливаются свойства базового HTML элемента. super(); // Берет <template> и хранит его в переменной `warinng` let warning = document.getElementById("warningtemplate"); // Хранит контент шаблона в переменной `mywarning` let mywarning = warning.content; const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true)); }});
Я оставил в коде подробные построчные комментарии, но не объяснил код на последней строке.
Код
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));
Мы здесь делаем много работы. Во-первых, мы берем наш
веб-компонент (this) и создаем скрытого шпиона, я имею в виду
Shadow DOM.{ mode: open }
означает, что JavaScript
может извне :root
обращаться к элементам Shadow DOM и
управлять ими, что-то вроде настройки доступа к компоненту через
черный вход. Был создан Shadow DOM и мы добавляем к ней узел
(Примечание переводчика: HTML Node). Этот узел будет
полной копией шаблона, включая все элементы и текст шаблона. С
шаблоном, прикрепленным к Shadow DOM из пользовательского
компонента, элемент <slot>
и slot атрибут берут
на себя задачу сопоставления содержимого с тем, где оно должно
находиться.
Проверьте это. Теперь мы можем объединить экземпляра одного и того же веб-компонента, отображая разный контент, просто изменив один элемент.
КодJS:
customElements.define('apocalyptic-warning', class extends HTMLElement { constructor() { super(); let warning = document.getElementById('warningtemplate'); let mywarning = warning.content; const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(mywarning.cloneNode(true)); } });
HTML:
<p>The Apocalypse will never happen!</p><apocalyptic-warning> <span slot="whats-coming">Undead</span></apocalyptic-warning><apocalyptic-warning> <span slot="whats-coming">Halitosis Laden Zombie Minions</span></apocalyptic-warning><template id="warningtemplate"> <style> p { background-color: pink; padding: 0.5em; border: 1px solid red; } </style> <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>
Пример: Codepen
Стилизация компонента
Возможно, вы заметили стилизацию в нашем примере. Как и
следовало ожидать, у нас есть абсолютно все возможности для
стилизации наших компонентов в CSS. На самом деле, мы можем
включить <style>
элемент прямо в
<template>
.
<template id="warningtemplate"> <style> p { background-color: pink; padding: 0.5em; border: 1px solid red; } </style> <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>
Таким образом, стили применяются только для компонента, что позволяет изолировать их благодаря теневой модели Shadow DOM.
Я предположил, что пользовательский компонент берет копию шаблона, вставляет контент, который вы добавили и внедряет его на страницу, используя Shadow DOM. Хотя может показаться, что работа Shadow DOM похожа на обычное DOM дерево, но это не так. Контент в веб-компоненте остается на месте, а теневой DOM как бы накладывается сверху, как наложение.
И с этого момента контент технически находится вне шаблона,
любые селекторы и классы, используемые в шаблонном
<style>
не будут действовать на контент внутри
<slot>
. Это не позволяет достичь полной
инкапсуляции, чего я ожидал. Но поскольку веб-компонент является
элементом, мы можем использовать его как селектор элемента в любом
старом CSS файле, включая основной файл, используемый на странице.
И хотя вставленный материал технически не находится в шаблоне, он
находится в веб-компоненте, где доступна стилизация через селекторы
потомков CSS.
apocalyptic-warning span { color: blue;}
Будьте осторожны, стили в основном файле CSS не могут получить
доступ к внутренним элементам <template>
.
Код JavaScript точно такой же, за исключением того, что сейчас
мы работаем с компонентом, у которого другое имя,
<zombie-profile>.
customElements.define("zombie-profile", class extends HTMLElement { constructor() { super(); let profile = document.getElementById("zprofiletemplate"); let myprofile = profile.content; const shadowRoot = this.attachShadow({mode: "open"}).appendChild(myprofile.cloneNode(true)); } });
Вот шаблон HTML и инкапсулированный CSS.
Код
<template id="zprofiletemplate"> <style> img { width: 100%; max-width: 300px; height: auto; margin: 0 1em 0 0; } h2 { font-size: 3em; margin: 0 0 0.25em 0; line-height: 0.8; } h3 { margin: 0.5em 0 0 0; font-weight: normal; } .age, .infection-date { display: block; } span { line-height: 1.4; } .label { color: #555; } li, ul { display: inline; padding: 0; } li::after { content: ', '; } li:last-child::after { content: ''; } li:last-child::before { content: ' and '; } </style> <div class="profilepic"> <slot name="profile-image"><img src="http://personeltest.ru/aways/assets.codepen.io/1804713/default.png" alt=""></slot> </div> <div class="info"> <h2><slot name="zombie-name" part="zname">Zombie Bob</slot></h2> <span class="age"><span class="label">Age:</span> <slot name="z-age">37</slot></span> <span class="infection-date"><span class="label">Infection Date:</span> <slot name="idate">September 12, 2025</slot></span> <div class="interests"> <span class="label">Interests: </span> <slot name="z-interests"> <ul> <li>Long Walks on Beach</li> <li>brains</li> <li>defeating humanity</li> </ul> </slot> </div> <span class="z-statement"><span class="label">Apocalyptic Statement: </span> <slot name="statement">Moooooooan!</slot></span> </div></template>
Вот CSS для нашего <zombie-profile>
элемента
и его потомков из нашего основного файла CSS. Обратите внимание,
что мы используем дублирование стилей, чтобы обеспечить одинаковую
стилизацию и у замененных элементов, и у элементов
<template>
.
zombie-profile { width: calc(50% - 1em); border: 1px solid red; padding: 1em; margin-bottom: 2em; display: grid; grid-template-columns: 2fr 4fr; column-gap: 20px;}zombie-profile img { width: 100%; max-width: 300px; height: auto; margin: 0 1em 0 0;}zombie-profile li, zombie-profile ul { display: inline; padding: 0;}zombie-profile li::after { content: ', ';}zombie-profile li:last-child::after { content: '';}zombie-profile li:last-child::before { content: ' and ';}
А вот и результат:
Пример: Codepen
Несмотря на то, что есть еще пару ловушек и нюансов, я надеюсь, вы получили больше возможностей для работы с веб-компонентами, чем несколько минут назад. Возможно, будет хорошо, если вы добавите в свою работу какой-то нестандартный компонент, чтобы прочувствовать их и понять, где их использование имеет смысл.
Вот и все. Чего вы сейчас боитесь больше: веб-компонентов или зомби-апокалипсиса? В недалеком прошлом я бы мог сказать, что веб-компонентов, но теперь зомби - единственное, что меня беспокоит (Ну, и покроют ли мои суточные выплаты закуски).