От автора: когда я ходил на конференции и смотрел, как кто-то делает презентацию о веб-компонентах, я всегда думал, что это было довольно здорово (да, очевидно, я из 1950 года), и это всегда казалось мне очень сложным. Тысяча строк JavaScript, чтобы сэкономить четыре строки HTML. Докладчик неизбежно либо замалчивал уйму JavaScript, который заставляет это работать, либо они вдавались в мучительные детали, и мои глаза наполнялись грустью от непонимания того, о чем говорят.
Но в недавнем справочном проекте, призванном упростить изучение HTML (конечно, путем проб и ошибок), я решил, что мне нужно познакомится с каждым элементом HTML в спецификации. Помимо этих конференций, это было мое первое знакомство с элементами и . Но для написания чего-нибудь простенького и увлекательного, пришлось копнуть немного глубже.
И я кое-что узнал в процессе: веб-компоненты намного проще, чем кажутся. Либо веб-компоненты прошли долгий путь с тех пор, как я в последний раз поймал себя на мечтах о работе с ними на конференции, либо я позволил своему первоначальному страху перед ними помешать по-настоящему узнать их — возможно, и то и другое.
Я здесь, чтобы сказать вам, что вы — да, вы — можете создать веб-компонент. Давайте на мгновение оставим наши отвлекающие факторы и страхи, давайте сделаем это вместе.
Начнем с
A — это элемент HTML, который позволяет нам создать, шаблон — структуру HTML для веб-компонента. Шаблон не обязательно должен быть огромным фрагментом кода. Он может быть очень простой:
The Zombies are coming!
123 |
The Zombies are coming! |
Элемент имеет важное значение, потому что он связывает все вместе. Это как фундамент здания; это база, на которой построено все остальное. Давайте использовать этот небольшой фрагмент HTML в качестве шаблона для нашего веб-компонента — в качестве предупреждения, когда на нас надвигается зомби-апокалипсис.
Элемент
это просто еще один элемент HTML, как и . Но в этом случае настраивает то, как отображается на странице.
The Zombies are coming!
123 |
The Zombies are coming! |
Здесь мы вставили слово «Zombies» в шаблонную разметку. Если мы ничего не делаем с тегом slot, по умолчанию используется содержимое между тегами.
Использование slot очень похоже на заполнитель. Мы можем использовать заполнитель как есть или вместо него указать что-нибудь другое. Мы делаем это с помощью атрибута name.
The Zombies are coming!
123 |
The Zombies are coming! |
Атрибут name сообщает о веб-компоненте, содержание которого описано в шаблоне. Прямо сейчас у нас есть слот под названием whats-coming. Мы предполагаем, что зомби придут первыми в апокалипсисе, но этот slot дает нам некоторую гибкость, чтобы вставить что-то еще, например, если это будет робот, оборотень или даже апокалипсисом веб-компонентов.
Преобразование объекта с фиксированными размерами в адаптивный элемент
Использование компонента
Технически мы закончили «писать» компонент и можем вставить его в любое место, где захотим.
Halitosis Laden Undead Minions
The Zombies are coming!
1234567 | Halitosis Laden Undead Minions
The Zombies are coming! |
Видите, что мы там сделали? Мы поместили компонент на страницу точно так же, как любой другой div или что-то еще. Но мы также добавили в span ссылку на name атрибут нашего slot. И то, что между ними, в span мы можем заменить на «Zombies» при рендеринге компонента.
Вот небольшая ошибка, на которую стоит обратить внимание: в именах пользовательских элементов должен быть дефис. Это просто одна из тех вещей, которые вам нужно знать. Спецификация предписывает это для предотвращения конфликтов в случае, если в HTML появится элемент с тем же именем.
Вы все еще со мной? Не так уж и страшно, правда? Ну, если не считать зомби. Нам еще предстоит немного поработать, чтобы сделать замену slot возможной, и именно здесь мы начинаем разбираться с JavaScript.
Регистрация компонента
Как я уже сказал, вам нужен некоторый JavaScript, чтобы все это работало, но это не сверхсложный, многострочный и глубокий код, как я думал раньше. Надеюсь, я смогу убедить и вас. Вам нужна функция-конструктор, регистрирующая пользовательский элемент. Вот конструктор, который мы будем использовать:
JavaScript // Определяет элемент с нашим именем,
customElements.define(“apocalyptic-warning”,
// Гарантирует, что у нас есть все свойства и методы по умолчанию встроенного элемента HTML
class extends HTMLElement {
// Вызывается каждый раз, когда создается новый пользовательский элемент
constructor() {
// Вызывает родительский конструктор, то есть конструктор для элемента HTML, чтобы все было настроено точно так же, как и при создании встроенного HTML-элемента.
super();
// Берёт и сохраняет его в `warning`
let warning = document.getElementById(“warningtemplate”);
// Сохраняет содержимое шаблона в `mywarning`
let mywarning = warning.content;
const shadowRoot = this.attachShadow({mode: “open”}).appendChild(mywarning.cloneNode(true)); }
});
123456789101112131415161718192021 | // Определяет элемент с нашим именем, customElements.define(“apocalyptic-warning”, // Гарантирует, что у нас есть все свойства и методы по умолчанию встроенного элемента HTML class extends HTMLElement { // Вызывается каждый раз, когда создается новый пользовательский элемент constructor() { // Вызывает родительский конструктор, то есть конструктор для элемента HTML, чтобы все было настроено точно так же, как и при создании встроенного HTML-элемента. super(); // Берёт и сохраняет его в `warning` let warning = document.getElementById(“warningtemplate”); // Сохраняет содержимое шаблона в `mywarning` let mywarning = warning.content; const shadowRoot = this.attachShadow({mode: “open”}).appendChild(mywarning.cloneNode(true)); } }); |
Я привёл подробные комментарии, которые объясняют все построчно. Кроме последней строки:
JavaScript const shadowRoot = this.attachShadow({mode: “open”}).appendChild(mywarning.cloneNode(true));
1 | const shadowRoot = this.attachShadow({mode: “open”}).appendChild(mywarning.cloneNode(true)); |
Мы здесь много делаем. Во-первых, мы берем наш custom element (this) и создаем скрытого агента — я имею в виду теневой DOM. mode: open просто означает, что JavaScript извне :root может обращаться к элементам в теневой DOM и манипулировать ими, что-то вроде настройки доступа к компоненту через черный ход.
Полноэкранное меню на CSS с эффектом анимации
Была создана теневая DOM, и мы добавили к ней узел. Этот узел есть полной копией шаблона, включая все элементы и текст шаблона. Из шаблона, прикрепленного к теневому DOM пользовательского элемента, подбирается содержимое атрибута slot и подставляется на свое место.
Проверь это. Теперь мы можем объединить два экземпляра одного и того же компонента, отображая разный контент, просто изменив один элемент.
Стилизация компонента
Возможно, вы заметили стиль в этой демонстрации. Как и следовало ожидать, у нас есть абсолютная возможность стилизовать наш компонент с помощью CSS. Фактически, мы можем включить элемент style прямо в template.
p { background-color: pink; padding: 0.5em; border: 1px solid red; }
The Zombies are coming!
1234567891011 | p { background-color: pink; padding: 0.5em; border: 1px solid red; }
The Zombies are coming! |
Таким образом, стили ограничиваются непосредственно компонентом, и ничего не просачивается в другие элементы на той же странице благодаря теневой модели DOM.
Я предполагал, что пользовательский элемент берет копию шаблона, вставляет добавленный нами контент, а затем внедряет его на страницу с помощью теневой модели DOM. Хотя так это выглядит во внешнем интерфейсе, на самом деле это не верно. Контент в пользовательском элементе остается прямо на месте, а теневой DOM как бы накладывается сверху, как наложение.
А поскольку содержимое технически находится за пределами шаблона, любые дочерние селекторы или классы, которые мы используем в элементе шаблона style, не будут влиять на содержимое, размещенное в slot. Это не позволяет обеспечить полную инкапсуляцию, как я ожидал. Но поскольку пользовательский элемент является элементом, мы можем использовать его в качестве селектора элемента в любом файле CSS, включая основную таблицу стилей, используемую на странице. И хотя вставленный материал технически не находится в шаблоне, он находится в пользовательском элементе, и селекторы потомков из CSS будут работать.
CSS apocalyptic-warning span { color: blue;
}
123 | apocalyptic-warning span { color: blue;} |
Но будьте осторожны! Стили в основном файле CSS не могут получить доступ к элементам в template теневой модели DOM.
Соберем все это вместе
Давайте посмотрим на пример, скажем, профиль для службы знакомств с зомби, вроде того, который вам может понадобиться после апокалипсиса. Чтобы стилизовать и содержимое по умолчанию и любой вставленный контент, нам нужен элемент style в template и стили в файле CSS.
Код JavaScript точно такой же, за исключением того, что сейчас мы работаем с другим именем компонента, .
JavaScript 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)); } }
);
Искусственный интеллекту упростит работу с данными в Google Таблицах
12345678910 | 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:
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 ‘; }
Zombie Bob
Age: 37
Infection Date: September 12, 2025
Interests:
- Long Walks on Beach
- brains
- defeating humanity
Apocalyptic Statement: Moooooooan!
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566 | 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 ‘; }
Zombie BobAge: 37 Infection Date: September 12, 2025 Interests:
Apocalyptic Statement: Moooooooan! |
Вот CSS для нашего элемента и его потомков из нашего основного файла CSS. Обратите внимание на дублирование, чтобы гарантировать, что и замененные элементы, и элементы из шаблона имеют одинаковый стиль.
CSS 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 ‘;
}
12345678910111213141516171819202122232425262728 | 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 ‘;} |
Теперь все собрано вместе!
Несмотря на то, что есть еще несколько ошибок и других нюансов, я надеюсь, что вы увидели больше возможностей работать с веб-компонентами сейчас, чем несколько минут назад. Возможно, добавьте в свою работу нестандартный компонент, чтобы почувствовать его и понять, где это имеет смысл.
Это действительно так. Чего вы боитесь больше, веб-компонентов или зомби-апокалипсиса? Я мог бы сказать, что в недалеком прошлом боялся веб-компонентов, но теперь я с гордостью могу сказать, что зомби — единственное, что меня беспокоит.
Автор: John Rhea
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен
Источник