Как работает JS: пользовательские элементы

в 11:04, , рубрики: custom elements, javascript, Блог компании RUVDS.com, пользовательские элементы, разработка, Разработка веб-сайтов
[Советуем почитать] Предыдущие 18 частей цикла

Часть 1: Обзор движка, механизмов времени выполнения, стека вызовов
Часть 2: О внутреннем устройстве V8 и оптимизации кода
Часть 3: Управление памятью, четыре вида утечек памяти и борьба с ними
Часть 4: Цикл событий, асинхронность и пять способов улучшения кода с помощью async / await
Часть 5: WebSocket и HTTP/2+SSE. Что выбрать?
Часть 6: Особенности и сфера применения WebAssembly
Часть 7: Веб-воркеры и пять сценариев их использования
Часть 8: Сервис-воркеры
Часть 9: Веб push-уведомления
Часть 10: Отслеживание изменений в DOM с помощью MutationObserver
Часть 11: Движки рендеринга веб-страниц и советы по оптимизации их производительности
Часть 12: Сетевая подсистема браузеров, оптимизация её производительности и безопасности
Часть 12: Сетевая подсистема браузеров, оптимизация её производительности и безопасности
Часть 13: Анимация средствами CSS и JavaScript
Часть 14: Как работает JS: абстрактные синтаксические деревья, парсинг и его оптимизация
Часть 15: Как работает JS: классы и наследование, транспиляция в Babel и TypeScript
Часть 16: Как работает JS: системы хранения данных
Часть 17: Как работает JS: технология Shadow DOM и веб-компоненты
Часть 18: Как работает JS: WebRTC и механизмы P2P-коммуникаций

Представляем вашему вниманию перевод 19 статьи из серии материалов компании SessionStack, посвящённых особенностям различных механизмов экосистемы JavaScript. Сегодня речь пойдёт о стандарте Custom Elements — о так называемых «пользовательских элементах». Мы поговорим, о том, какие задачи они позволяют решать, и о том, как их создавать и использовать.

image


Обзор

В одном из предыдущих материалов этой серии мы говорили о Shadow DOM и о некоторых других технологиях, которые являются частью более крупного явления — веб-компонентов. Веб-компоненты нацелены на то, чтобы дать разработчикам возможность расширять стандартные возможности HTML, создавая компактные, модульные и подходящие для повторного использования элементы. Это — сравнительно новый стандарт W3C, на который уже обратили внимание производители всех ведущих браузеров. Его можно встретить в продакшне, хотя, конечно, пока его работу обеспечивают полифиллы (о них мы поговорим позже).

Как вы, возможно, уже знаете, браузеры дают нам несколько важнейших средств для разработки веб-сайтов и веб-приложений. Речь идёт об HTML, CSS и JavaScript. HTML используют для структурирования веб-страниц, благодаря CSS им придают приятный внешний вид, а JavaScript отвечает за интерактивные возможности. Однако до появления веб-компонентов связывать действия, реализуемые средствами JavaScript, с HTML-структурой, было не так уж и легко.

Собственно говоря, здесь мы рассмотрим основу веб-компонентов — пользовательские элементы (Custom Elements). Если рассказать о них в двух словах, то API, предназначенное для работы с ними, позволяет программисту создавать собственные HTML-элементы со встроенной в них JavaScript-логикой и стилями, описанными средствами CSS. Многие путают пользовательские элементы с технологией Shadow DOM. Однако, это — две совершенно разные вещи, которые, на самом деле, дополняют друг друга, но не являются взаимозаменяемыми.

Некоторые фреймворки (такие, как Angular или React) пытаются решить ту же проблему, которую решают пользовательские элементы, вводя собственные концепции. Пользовательские элементы можно сравнить с директивами Angular или с компонентами React. Однако пользовательские элементы — это стандартная возможность браузера, для работы с ними не нужно ничего, кроме обычных JavaScript, HTML и CSS. Конечно, это не позволяет говорить о том, что они являются заменой для обычных JS-фреймворков. Современные фреймворки дают нам гораздо большее, нежели лишь возможность имитировать поведение пользовательских элементов. В результате можно говорить о том, что и фреймворки, и пользовательские элементы — это технологии, которые можно использовать совместно для решения задач веб-разработки.

API

Прежде чем мы продолжим, давайте посмотрим, какие возможности нам даёт API для работы с пользовательскими элементами. А именно, речь идёт о глобальном объекте customElements, который имеет несколько методов:

  • Метод define(tagName, constructor, options) позволяет определить (создать, зарегистрировать) новый пользовательский элемент. Он принимает три аргумента — имя тега для пользовательского элемента, соответствующее правилам именования таких элементов, объявление класса и объект с параметрами. В настоящий момент поддерживается лишь один параметр — extends, который представляет собой строку, задающую имя встроенного элемента, который планируется расширить. Эта возможность используется для создания особых версий стандартных элементов.
  • Метод get(tagName) возвращает конструктор пользовательского элемента при условии, что этот элемент уже определён, в противном случае он возвращает undefined. Он принимает один аргумент — имя тега пользовательского элемента.
  • Метод whenDefined(tagName) возвращает промис, который разрешается после того, как пользовательский элемент будет создан. Если элемент уже определён, этот промис разрешается немедленно. Промис отклоняется, если переданное ему имя тега не является допустимым именем тега пользовательского элемента. Этот метод принимает имя тега пользовательского элемента.

Создание пользовательских элементов

Создавать пользовательские элементы очень просто. Для этого надо сделать две вещи: создать объявление класса для элемента, который должен расширять класс HTMLElement и зарегистрировать этот элемент под выбранным именем. Вот как это выглядит:

class MyCustomElement extends HTMLElement {
  constructor() {
    super();
    // …
  }

  // …
}

customElements.define('my-custom-element', MyCustomElement);

Если вы не хотите загрязнять текущую область видимости, можете воспользоваться анонимным классом:

customElements.define('my-custom-element', class extends HTMLElement {
  constructor() {
    super();
    // …
  }

  // …
});

Как можно видеть из примеров, регистрация пользовательского элемента производится с помощью уже знакомого вам метода customElements.define(...).

Проблемы, которые решают пользовательские элементы

Поговорим о проблемах, которые позволяют решать пользовательские элементы. Одна из них — это улучшение структуры кода и устранение того, что называют «супом из тегов div» (div soup). Это явление представляет собой весьма распространённую в современных веб-приложениях структуру кода, в которой имеется множество вложенных друг в друга элементов div. Вот как это может выглядеть:

<div class="top-container">
  <div class="middle-container">
    <div class="inside-container">
      <div class="inside-inside-container">
        <div class="are-we-really-doing-this">
          <div class="mariana-trench">
            …
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Подобный HTML-код используют по вполне оправданным причинам — он описывает устройство страницы и обеспечивает её правильный вывод на экран. Однако это ухудшает читабельность HTML-кода и усложняет его сопровождение.

Предположим, у нас имеется компонент, выглядящий так, как показано на следующем рисунке.

Как работает JS: пользовательские элементы - 2

Внешний вид компонента

При использовании традиционного подхода к описанию подобных вещей этому компоненту будет соответствовать следующий код:

<div class="primary-toolbar toolbar">
  <div class="toolbar">
    <div class="toolbar-button">
      <div class="toolbar-button-outer-box">
        <div class="toolbar-button-inner-box">
          <div class="icon">
            <div class="icon-undo"> </div>
          </div>
        </div>
      </div>
    </div>
    <div class="toolbar-button">
      <div class="toolbar-button-outer-box">
        <div class="toolbar-button-inner-box">
          <div class="icon">
            <div class="icon-redo"> </div>
          </div>
        </div>
      </div>
    </div>
    <div class="toolbar-button">
      <div class="toolbar-button-outer-box">
        <div class="toolbar-button-inner-box">
          <div class="icon">
            <div class="icon-print"> </div>
          </div>
        </div>
      </div>
    </div>
    <div class="toolbar-toggle-button toolbar-button">
      <div class="toolbar-button-outer-box">
        <div class="toolbar-button-inner-box">
          <div class="icon">
            <div class="icon-paint-format"> </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

А теперь представьте себе, что мы могли бы, вместо этого кода, воспользоваться вот таким описанием компонента:

<primary-toolbar>
  <toolbar-group>
    <toolbar-button class="icon-undo"></toolbar-button>
    <toolbar-button class="icon-redo"></toolbar-button>
    <toolbar-button class="icon-print"></toolbar-button>
    <toolbar-toggle-button class="icon-paint-format"></toolbar-toggle-button>
  </toolbar-group>
</primary-toolbar>

Уверен, все согласятся с тем, что второй фрагмент кода выглядит куда лучше. Такой код легче читать, легче поддерживать, он понятен и разработчику, и браузеру. Всё сводится к тому, что он — проще, чем тот, в котором имеются множество вложенных тегов div.

Следующая проблема, которую можно решить с помощью пользовательских элементов — это повторное использование кода. Код, который пишут разработчики, должен быть не только работающим, но и поддерживаемым. Повторное использование кода, в противовес постоянному написанию одних и тех же конструкций, улучшает возможности по поддержке проектов.
Вот простой пример, который позволит лучше разобраться в этой идее. Предположим, у нас имеется следующий элемент:

<div class="my-custom-element">
  <input type="text" class="email" />
  <button class="submit"></button>
</div>

Если в нём постоянно возникает необходимость, то, при обычном подходе, нам снова и снова придётся писать один и тот же HTML-код. Теперь представьте, что в этот код надо внести изменение, которое должно отразиться везде, где он используется. Это означает, что нам надо найти все места, где используется этот фрагмент, после чего везде внести в него одни и те же изменения. Это долго, тяжело и чревато ошибками.

Было бы куда лучше, если бы мы могли там, где нужен этот элемент, просто написать следующее:

<my-custom-element></my-custom-element>

Однако современные веб-приложения — это гораздо больше, чем статический HTML-код. Они интерактивны. Источником их интерактивности является JavaScript. Обычно, для обеспечения подобных возможностей, создают некие элементы, потом подключают к ним прослушиватели событий, что позволяет им реагировать на воздействия пользователя. Например, они могут реагировать на щелчки, на «зависание» над ними указателя мыши, на перетаскивание их по экрану, и так далее. Вот как к элементу подключают прослушиватель события, возникающего при щелчке по нему мышью:

var myDiv = document.querySelector('.my-custom-element');

myDiv.addEventListener('click', _ => {
  myDiv.innerHTML = '<b> I have been clicked </b>';
});

А вот HTML-код этого элемента:

<div class="my-custom-element">
  I have not been clicked yet.
</div>

Благодаря использованию API для работы с пользовательскими элементами вся эта логика может быть включена в сам элемент. Для сравнения — ниже показан код объявления пользовательского элемента, включающего в себя обработчик событий:

class MyCustomElement extends HTMLElement {
  constructor() {
    super();

    var self = this;

    self.addEventListener('click', _ => {
      self.innerHTML = '<b> I have been clicked </b>';
    });
  }
}

customElements.define('my-custom-element', MyCustomElement);

А вот как он выглядит в HTML-коде страницы:

<my-custom-element>
  I have not been clicked yet
</my-custom-element>

На первый взгляд может показаться, что для создания пользовательского элемента требуется больше строк JS-кода. Однако в реальных приложениях редко когда бывает так, чтобы подобные элементы создавали бы лишь для того, чтобы воспользоваться ими только один раз. Ещё одно типичное в современных веб-приложениях явление заключается в том, что большинство элементов в них создаётся динамически. Это приводит к необходимости поддержки двух различных сценариев работы с элементами — ситуаций, когда они добавляются на страницу динамически, средствами JavaScript, и ситуаций, когда они описаны в исходной HTML-структуре страницы. Благодаря применению пользовательских элементов работа в этих двух ситуациях упрощается.

В результате, если подвести краткие итоги этого раздела, можно сказать, что пользовательские элементы делают код понятнее, упрощают его поддержку, способствуют разбиению его на маленькие модули, включающие в себя весь необходимый функционал и подходящие для повторного использования.

Теперь, когда мы обсудили общие вопросы работы с пользовательскими элементами, поговорим об их особенностях.

Требования

Прежде чем вы приступите к разработке собственных пользовательских элементов, вам стоит знать о некоторых правилах, которым надо следовать при их создании. Вот они:

  • Имя компонента должно включать в себя дефис (символ -). Благодаря этому HTML-парсер может различать встроенные и пользовательские элементы. Кроме того, такой подход обеспечивает отсутствие коллизий имён со встроенными элементами (и с теми, что есть сейчас, и с теми, которые появятся в будущем). Например, действительное имя пользовательского элемента — это >my-custom-element<, а имена >myCustomElement< и <my_custom_element> являются неподходящими.
  • Запрещено более одного раза регистрировать один и тот же тег. Попытка сделать это приведёт к выдаче браузером ошибки DOMException. Пользовательские элементы нельзя переопределять.
  • Пользовательские теги не могут быть самозакрывающимися. HTML-парсер поддерживает лишь ограниченный набор стандартных самозакрывающихся тегов (например — <img>, <link>, <br>).

Возможности

Поговорим о том, что можно делать с пользовательскими элементами. Если в двух словах ответить на этот вопрос, то окажется, что делать с ними можно очень много всего интересного.

Одна из самых заметных возможностей пользовательских элементов заключается в том, что объявление класса элемента относится к самому DOM-элементу. Это означает, что в объявлении можно использовать ключевое слово this для подключения прослушивателей событий, для доступа к свойствам, к дочерним узлам, и так далее.

class MyCustomElement extends HTMLElement {
  // ...

  constructor() {
    super();

    this.addEventListener('mouseover', _ => {
      console.log('I have been hovered');
    });
  }

  // ...
}

Это, конечно, даёт возможность записывать в дочерние узлы элемента новые данные. Однако делать подобное не рекомендуется, так как это может привести к неожиданному поведению элементов. Если вы представите, что вы пользуетесь элементами, которые разработаны кем-то другим, то вы, наверняка, удивитесь, если вашу собственную разметку, помещённую в элемент, заменят на что-то другое.

Существует несколько методов, которые позволяют выполнять код в определённые моменты жизненного цикла элемента.

  • Метод constructor вызывается один раз, при создании или «обновлении» (upgrade) элемента (об этом мы поговорим ниже). Чаще всего он используется для инициализации состояния элемента, для подключения прослушивателей событий, создания Shadow DOM, и так далее. Не забывайте о том, что в конструкторе всегда нужно вызывать super().
  • Метод connectedCallback вызывается каждый раз, когда элемент добавляется в DOM. Его можно использовать (и именно так и рекомендуется его использовать) для того, чтобы откладывать выполнение каких-либо действий до момента, когда элемент окажется на странице (например, так можно отложить загрузку каких-то данных).
  • Метод disconnectedCallback вызывается, когда элемент удаляется из DOM. Обычно он используется для освобождения ресурсов. Учитывайте, что этот метод не вызывается, если пользователь закрывает вкладку браузера со страницей. Поэтому не полагайтесь на него при необходимости выполнения каких-то особенно важных действий.
  • Метод attributeChangedCallback вызывается, когда добавляется, удаляется, обновляется или заменяется атрибут элемента. Кроме того, он вызывается при создании элемента парсером. Однако обратите внимание на то, что этот метод применяется лишь для атрибутов, которые перечислены в свойстве observedAttributes.
  • Метод adoptedCallback вызывается при вызове метода document.adoptNode(...), используемого для перемещения узла в другой документ.

Обратите внимание на то, что все вышеописанные методы являются синхронными. Например, метод connectedCallback вызывается немедленно после того, как элемент будет добавлен в DOM, и вся остальная программа ждёт окончания выполнения этого метода.

Отражение свойств

У встроенных HTML-элементов есть одна очень удобная возможность: отражение свойств (property reflection). Благодаря этому механизму значения некоторых свойств напрямую отражаются в DOM в виде атрибутов. Скажем, это характерно для свойства id. Например, выполним такую операцию:

myDiv.id = 'new-id';

Соответствующие изменения затронут и DOM:

<div id="new-id"> ... </div>

Этот механизм действует и в обратном направлении. Он весьма полезен, так как позволяет декларативно конфигурировать элементы.

У пользовательских элементов нет подобной встроенной возможности, но её можно реализовать самостоятельно. Для того чтобы некоторые свойства пользовательских элементов вели себя похожим образом, можно настроить их геттеры и сеттеры.

class MyCustomElement extends HTMLElement {
  // ...

  get myProperty() {
    return this.hasAttribute('my-property');
  }

  set myProperty(newValue) {
    if (newValue) {
      this.setAttribute('my-property', newValue);
    } else {
      this.removeAttribute('my-property');
    }
  }

  // ...
}

Расширение существующих элементов

API пользовательских элементов позволяет не только создавать новые HTML-элементы, но и расширять существующие. Причём, речь идёт и о стандартных элементах, и о пользовательских. Делается это с помощью использования ключевого слова extends при объявлении класса:

class MyAwesomeButton extends MyButton {
  // ...
}

customElements.define('my-awesome-button', MyAwesomeButton);</cosourcede>
В случае со стандартными элементами нужно, кроме того, использовать, при вызове метода <code>customElements.define(...)</code>, объект со свойством <code>extends</code> и со значением, представляющим собой имя тега расширяемого элемента. Это сообщает браузеру о том, какой именно элемент является основой нового пользовательского элемента, так как множество встроенных элементов имеют одинаковые DOM-интерфейсы. Без указания того, какой именно элемент используется в качестве основы для пользовательского элемента, браузер не будет знать о том, на какой именно функциональности базируется новый элемент.

<source>class MyButton extends HTMLButtonElement {
  // ...
}

customElements.define('my-button', MyButton, {extends: 'button'});

Расширенные стандартные элементы ещё называются «кастомизированными встроенными элементами» (customized built-in element).

Рекомендуется принять за правило всегда расширять существующие элементы, и делать это прогрессивно. Это позволит вам сохранить в новых элементах возможности, которые были реализованы в ранее созданных элементах (то есть — свойства, атрибуты, функции).

Обратите внимание на то, что сейчас кастомизированные встроенные элементы поддерживаются лишь в Chrome 67+. Это появится и в других браузерах, однако, известно, что разработчики Safari решили эту возможность не реализовывать.

Обновление элементов

Как уже было сказано, метод customElements.define(...) используется для регистрации пользовательских элементов. Однако регистрацию нельзя назвать тем действием, которое нужно выполнять в первую очередь. Регистрацию пользовательского элемента можно на некоторое время отложить, причём, это время может настать даже тогда, когда элемент уже добавлен в DOM. Этот процесс называют обновлением элемента (upgrade). Для того чтобы узнать, когда элемент будет зарегистрирован, браузер предоставляет метод customElements.whenDefined(...). Ему передают имя тега элемента, а он возвращает промис, который разрешается после регистрации элемента.

customElements.whenDefined('my-custom-element').then(_ => {
  console.log('My custom element is defined');
});

Например, может понадобиться отложить регистрацию элемента до того момента, как будут объявлены его дочерние элементы. Подобная линия поведения может оказаться крайне полезной в том случае, если в проекте имеются вложенные пользовательские элементы. Иногда родительский элемент может полагаться на реализацию дочерних элементов. В данном случае нужно обеспечить, чтобы дочерние элементы были бы зарегистрированы до родительского.

Shadow DOM

Как уже было сказано, пользовательские элементы и Shadow DOM — это взаимодополняющие технологии. Первая позволяет инкапсулировать в пользовательских элементах JS-логику, а вторая позволяет создавать изолированные окружения для фрагментов DOM, на которые не влияет то, что находится за их пределами. Если вы чувствуете, что вам нужно лучше разобраться с концепцией Shadow DOM — взгляните на одну из наших предыдущих публикаций.

Вот как использовать Shadow DOM для пользовательского элемента:

class MyCustomElement extends HTMLElement {
  // ...

  constructor() {
    super();

    let shadowRoot = this.attachShadow({mode: 'open'});
    let elementContent = document.createElement('div');
    shadowRoot.appendChild(elementContent);
  }

  // ...
});

Как видите, здесь ключевую роль играет вызов this.attachShadow.

Шаблоны

В одном из наших предыдущих материалов мы немного говорили о шаблонах, хотя они, на самом деле, достойны отдельной статьи. Здесь мы рассмотрим простой пример того, как встраивать шаблоны в пользовательские элементы при их создании. Так, используя тег <template>, можно описать фрагмент DOM, который будет обработан парсером, но на страницу выведен не будет:

<template id="my-custom-element-template">
  <div class="my-custom-element">
    <input type="text" class="email" />
    <button class="submit"></button>
  </div>
</template>

Вот как применить шаблон в пользовательском элементе:

let myCustomElementTemplate = document.querySelector('#my-custom-element-template');

class MyCustomElement extends HTMLElement {
  // ...

  constructor() {
    super();

    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(myCustomElementTemplate.content.cloneNode(true));
  }

  // ...
});

Как видите, здесь имеется комбинация пользовательского элемента, Shadow DOM и шаблонов. Это позволило создать элемент, изолированный в собственном пространстве, в котором HTML-структура отделена от JS-логики.

Стилизация

До сих пор мы говорили лишь о JavaScript и HTML, обойдя вниманием CSS. Поэтому сейчас затронем тему стилей. Очевидно то, что нам нужен какой-то способ стилизации пользовательских элементов. Стили можно добавлять внутрь Shadow DOM, но тогда возникает вопрос о том, как стилизовать такие элементы извне, например — если пользоваться ими будет не тот, кто их создал. Ответ на этот вопрос достаточно прост — стилизуют пользовательские элементы точно так же, как и встроенные.

my-custom-element {
  border-radius: 5px;
  width: 30%;
  height: 50%;
  // ...
}

Обратите внимание на то, что внешние стили имеют более высокий приоритет, чем стили, объявленные внутри элемента, переопределяя их.

Возможно, вам доводилось видеть, как, во время вывода страницы на экран, в какой-то момент на ней можно наблюдать нестилизованное содержимое (это то, что называется FOUC — Flash Of Unstyled Content). Избежать этого явления можно, задавая стили для незарегистрированных компонентов, и используя некие визуальные эффекты при их регистрации. Для этого можно использовать селектор :defined. Сделать это, например, можно так:

my-button:not(:defined) {
  height: 20px;
  width: 50px;
  opacity: 0;
}

Неизвестные элементы и неопределённые пользовательские элементы

Спецификация HTML отличается большой гибкостью, она позволяет объявлять любые необходимые разработчику теги. И, если тег не распознаётся браузером, он будет обработан парсером как HTMLUnknownElement:

var element = document.createElement('thisElementIsUnknown');

if (element instanceof HTMLUnknownElement) {
  console.log('The selected element is unknown');
}

Однако при работе с пользовательскими элементами подобная схема не применяется. Помните, мы говорили о правилах именования таких элементов? Когда браузер встречает подобный элемент, имеющий правильно сформированное имя, он будет обработан парсером как HTMLElement и будет представлен браузером как неопределённый пользовательский элемент.

var element = document.createElement('this-element-is-undefined');

if (element instanceof HTMLElement) {
  console.log('The selected element is undefined but not unknown');
}

Хотя внешне HTMLElement и HTMLUnknownElement могут и не отличаться, о некоторых их особенностях, всё же, стоит помнить, так как они по-разному обрабатываются в парсере. От элемента, имеющего имя, соответствующее правилам именования пользовательских элементов, ожидается наличие его реализации. До его регистрации такой элемент рассматривается как пустой элемент div. При этом неопределённый пользовательский элемент не реализует никаких методов или свойств встроенных элементов.

Поддержка браузерами

Поддержка первого варианта пользовательских элементов впервые появилась в Chrome 36+. Это было так называемое API Custom Components v0, которое теперь признано устаревшим, и, хотя оно всё ещё доступно, пользоваться им не рекомендуется. Если вам это API, всё же, интересно — взгляните на этот материал. API Custom Elements v1 доступно в Chrome 54+ и в Safari 10.1+ (хотя и не полностью). В Mozilla эта возможность присутствует начиная с v50, но по умолчанию она отключена, её нужно явным образом включать. Известно, что в Microsoft Edge ведётся работа по внедрению этого API. Надо сказать, что полностью пользовательские компоненты доступны лишь в браузерах, основанных на webkit. Однако, как уже было сказано, существуют полифиллы, которые позволяют работать с ними в любых браузерах — даже в IE 11.

Проверка возможности работы с пользовательскими элементами

Для того, чтобы узнать, поддерживает ли браузер работу с пользовательскими элементами, можно выполнить простую проверку на существование свойства customElements
в объекте window:

const supportsCustomElements = 'customElements' in window;

if (supportsCustomElements) {
  // API Custom Elements можно пользоваться
}

При использовании полифилла это выглядит так:

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    const script = document.createElement('script');

    script.src = src;
    script.onload = resolve;
    script.onerror = reject;

    document.head.appendChild(script);
  });
}

// Если полифилл нужен - выполняем его ленивую загрузку.
if (supportsCustomElements) {
  // Браузер поддерживает пользовательские элементы, с ними можно работать.
} else {
  loadScript('path/to/custom-elements.min.js').then(_ => {
    // Соответствующий полифилл загружен, с пользовательскими элементами можно работать.
  });
}

Итоги

В этом материале мы говорили о пользовательских элементах, которые дают разработчику следующие возможности:

  • Они позволяют привязать к HTML-элементу JavaScript-код, описывающий его поведение, и связать с ним его CSS-стилизацию.
  • Они дают возможность расширять существующие HTML-элементы (как встроенные, так и пользовательские).
  • Для работы с пользовательскими элементами не нужны дополнительные библиотеки или фреймворки. Всё, что нужно — это обычный JavaScript, HTML, CSS, и, если браузер не поддерживает пользовательские элементы, соответствующий полифилл.
  • Пользовательские элементы созданы в расчёте на их использование вместе с другими возможностями веб-компонентов (Shadow DOM, шаблоны, слоты, и так далее).
  • Поддержка пользовательских элементов тесно интегрирована в инструменты разработчика браузеров, в которых реализован этот стандарт.
  • При применении пользовательских элементов можно воспользоваться возможностями, уже имеющимися у других элементов.

Надо отметить, что поддержка стандарта Custom Elements v1 браузерами пока находится на среднем уровне, однако, всё указывает на то, что, в обозримом будущем, ситуация вполне может измениться к лучшему.

Уважаемые читатели! Планируете ли вы применять пользовательские элементы в своих проектах?

Как работает JS: пользовательские элементы - 3

Автор: ru_vds

Источник

Поделиться

* - обязательные к заполнению поля