Рубрики
Без рубрики

Давайте строим веб-компоненты! Часть 3: Ванильные компоненты

Вам не нужны необычные рамки или сложные инструменты для дизайна компонентов и создания приложений, вам просто нужен ваш веб-браузер!

Автор оригинала: Benny Powers.

Этот пост изначально опубликован на dev.to

Компонент ui – это все в наши дни. Знаете ли вы, что в Интернете есть свой собственный модуль нативного компонента, который не требует использования любых библиотек? Правдивая история! Вы можете написать, опубликовать и повторно использовать одно файловые компоненты, которые будут работать в любом * хороший браузер и В любых рамках (Если это ваша сумка).

В нашем Последнее сообщение Мы узнали о двухбиликах JavaScript, которые позволяют нам отправлять компоненты для браузеров, которые не поддерживают спецификации.

Сегодня мы получаем практику 👷♂️, мы создадим одно файловый веб-компонент без какой-либо библиотеки или рамочного кода. Мы собираемся написать элемент, который ленивый – загружает изображения, чтобы браузер только вытеснил только тогда, когда они появляются (или собираются появляться) на экране. Мы сделаем наш элемент доступны и кредитные плеча API S Arke ПересечениеОбсеревер Чтобы сделать это легкий и исполнительные средства Отказ Мы могли бы даже добавить некоторые дополнительные колокольчики и свистки, если мы чувствуем себя как это.

  • Пользовательский элемент класса
  • Обратные вызовы жизненного цикла
    • Конструктор
    • ConnectedCallback
    • AttributeChangedCallback.
    • ОтключенныйКоллбежок
    • УсыновленныйCallback.
    • Жизненный цикл страницы
  • Ленивая загрузка
  • Укладка нашего компонента
    • : хост и <слот>
    • CSS пользовательские свойства
  • Доступность
    • Расширение встроенных элементов
    • Доступные автономные элементы
  • Выводы

Давайте начнем! Откройте свой редактор и создайте файл под названием Lazy-Image.js Этот файл будет содержать наш компонент.

Пользовательский элемент класса

Также как мы видели в нашем первом посте веб-компонентов стандартов веб-компонентов, наш первый шаг будет инициализировать и зарегистрировать пользовательский класс элемента и предоставлять его базовым шаблоном. Позже мы улучшимся на шаблоне, добавив наше таможенное поведение.

const tagName = 'lazy-image';
const template = document.createElement('template');
template.innerHTML = ``;

class LazyImage extends HTMLElement {
  connectedCallback() {
    if (!this.shadowRoot) {
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
  }
}

const register = () => customElements.define(tagName, LazyImage);
window.WebComponents ? window.WebComponents.waitFor(register) : register();

Хорошо. Если вы следили с нашими предыдущими сообщениями, все должны казаться знакомыми, но небольшой обзор в порядке:

  1. Мы создаем элемент шаблона и определите тень нашего элемента DOM внутри него.
  2. Определим наш поведение пользовательского элемента в Класс Отказ
  3. Наш элемент ConnectedCallback Способ создает корень тени и маркирует шаблон в него.

PLOP, что в ваш документ и Giv’er:




  
    
    
  
  
    
  

Проверьте пример в https://glitch.me/sunset-sink

Увлекательно, верно? Хорошо, это скромное начало, но, по крайней мере, это работает. Если мы осмотрим наш элемент с инструментами DEV, мы можем видеть, что он содержит нашу Shadow DOM и связан с нашим пользовательским классом элемента.

DEV Tools DOM Inspector показывает наш пользовательский элемент с «пользовательским» значком рядом с ним, и корневой тени, содержащий элемент IMG

Что мало Пользовательские Значок – это путь Firefox рассказать нам, что это пользовательский элемент. Если вы нажмете на значок, отладчик будет открыт на определении вашего элемента. Хорошо сделано, Firefox DEV Tools Tool!

В следующем разделе мы действительно начнем готовить.

Обратные вызовы жизненного цикла

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

  1. ConnectedCallback ,
  2. AttributeChangedCallback ,
  3. Отключенcallback ,
  4. УсыновлениеCallback ,

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

Конструктор

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

// CustomElement's constructor runs
const el = document.createElement('custom-element');

Конструктор пользовательского элемента не должен иметь никаких параметров, и он должен позвонить Super () на первой строке своего тела, чтобы делегировать поведение на Htmlelement , Узел , так далее.; и связать это к экземпляру элемента. Конструктор не должен возвращать какое-либо значение, кроме undefined или это ;

// Don't do this
class BustedElement extends HTMLElement {
  constructor(bar) {
    this.foo = bar;
    return bar;
  }
}

// Do This
class DecentElement extends HTMLElement {
  constructor() {
    super();
    if (!window.bar) return;
    this.foo = window.bar;
  }
}

Возможно, вы захотите получить доступ к атрибутам вашего элемента Parentnode , дети и т. Д. В конструкторе, но не дайте искушению: если ваш элемент не подключен (то есть прилагается) к дереву DOM, он пока не будет обновлен, то есть у него еще нет детей или атрибуты. Ваш код будет работать в случае, когда элемент уже определен в документе, прежде чем элемент определен, но не удается в случае, когда JavaScript создает элемент.

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

По этим причинам лучше всего ограничивать активность конструктора для настройки внутреннего состояния, включая значения по умолчанию, а также при использовании полифиллирования, чтобы прикрепить корень тени и вызов Стильэмент в ConnectedCallback Отказ Просто обязательно проверить, если ShadowRoot Уже существует, или ошибка выкисляет в следующий раз, когда ваш элемент подключается (например, через Document.body.append (MyLazyImage) ).

// Don't do this
class BustedImage extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this.shadowImage = this.shadowRoot.getElementById('image');
    // OOPS! Light DOM attributes may not yet exist!
    this.shadowImage.src = this.getAttribute('src');
  }
}

// Do This
class LazyImage extends HTMLElement {
  constructor() {
    super();
    // Set default values of properties, as needed.
    this.src = '';
    // In order to work well with the polyfill,
    // We'll set up the DOM later on, when the element connects.
  }
}

ConnectedCallback

ConnectedCallback Уволен каждый раз, когда ваш элемент подключается к DOM, включая первый раз, который он обновлен. Это подходящий момент, чтобы настроить теневые дети и атрибуты.

const lazyImage = document.createElement('lazy-image'); // constructor runs
document.appendChild(lazyImage); // connectedCallback runs

const container = document.getElementById('container');
container.appendChild(lazyImage); // connectedCallback runs again
class LazyImage extends HTMLElement {
  constructor() {
    super();
    this.src = '';
    this.alt = '';
  }

  connectedCallback() {
    // Initialize properties that depend on light DOM
    this.src = this.getAttribute('src') || this.src;
    this.alt = this.getAttribute('alt') || this.alt;
    // Check if shadowRoot exists first
    if (!this.shadowRoot) {
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
      this.shadowImage = this.shadowRoot.getElementById('image')
    }
    // Set the shadow img attributes.
    this.shadowImage.src = this.src;
    this.shadowImage.alt = this.alt;
  }
}

Проверьте пример в https://glitch.me/adaptable-order

Ну, это обнадеживает. Мы создали нашу Shadow DOM и сделали некоторые основные сантехники, которые устанавливают нашу внутреннюю IMG Элемент SRC и Alt Атрибуты в соответствии с теми, которые нашли на нашем элементе, когда он был обновлен.

Мы хотим нашего ShadowImage ‘s SRC Атрибут, чтобы быть синхронизированным с нашими элементами, и мы также хотим синхронизировать эти атрибуты с SRC Дом недвижимости. С помощью AttributeChangedCallback И некоторые укладыши, мы сделаем это случиться.

AttributeChangedCallback.

Когда вы меняете SRC атрибут простой Элемент, браузер отвечает путем выбора и отображения нового изображения URL . Точно так же, когда вы используете JavaScript для установки SRC Свойство на объекте DOM элемента, новое значение отражено в атрибуте. Мы хотим, чтобы наш элемент вести себя так же. Спецификация html предоставляет AttributeChangedCallback Для таких видов использования.

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

static get observedAttributes() {
  return ['src', 'alt'];
}

С этим определенным, ваш элемент AttributeChangedCallback будет работать всякий раз, когда любой из SRC или Alt Изменение атрибутов. На данный момент мы будем просто прямыми значениями как свойства.

attributeChangedCallback(name, oldVal, newVal) {
  this[name] = newVal
}

Мы также хотим, чтобы наш элемент реагировать на изменения имущества, обновляя его ShadowImage, и, отражая новое значение для атрибута. Мы будем использовать загадки для этого:

class LazyImage extends HTMLElement {
  /**
   * Guards against loops when reflecting observed attributes.
   * @param  {String} name Attribute name
   * @param  {any} value
   * @protected
   */
  safeSetAttribute(name, value) {
    if (this.getAttribute(name) !== value) this.setAttribute(name, value);
  }

  /**
   * Image URI.
   * @type {String}
   */
  set src(value) {
    this.safeSetAttribute('src', value);
    // Set image src
    if (this.shadowImage) this.shadowImage.src = value;
  }

  get src() {
    return this.getAttribute('src')
  }

  /**
   * Image Alt tag.
   * @type {String}
   */
  set alt(value) {
    this.safeSetAttribute('alt', value);
    // Set image alt
    if (this.shadowImage) this.shadowImage.alt = value;
  }

  get alt() {
    return this.getAttribute('alt')
  }

  static get observedAttributes() {
    return ['src', 'alt'];
  }

  connectedCallback() {
    this.src = this.getAttribute('src');
    this.alt = this.getAttribute('alt');
    if (!this.shadowRoot) {
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
      this.shadowImage = this.shadowRoot.getElementById('image');
    }
  }

  attributeChangedCallback(name, oldVal, newVal) {
    this[name] = newVal;
  }
}

Проверьте пример в https://glitch.me/guttural-tarsier

Нажатие кнопки обновления SRC и Alt Свойства и атрибуты на пользовательском элементе, а также это тени для ребенка.

Инспектор показывает синхронизированные атрибуты

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

ОтключенныйКоллбежок

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

disconnectedCallback() {
  /* do cleanup stuff here */
}

Это будет удобно для нас позже, когда мы создаем ПересечениеОбсеревер Для каждого экземпляра нашего элемента. На данный момент мы оставим это как заглушку.

УсыновленныйCallback.

Пользовательские элементы также имеют УсыновлениеCallback который работает всякий раз, когда вы звоните Утверждать на пользовательском элементе, который внутри другого документа или фрагмента документа. В этом случае сначала элемент Отключенcallback будет работать, когда он отключается от своего оригинального документа, то УсыновлениеCallback и, наконец, ConnectedCallback Когда он подключается к вашему документу.

Гигант 🤷♂️ emoji.

Я думаю, что это было в основном предназначено для несуществующего HTML Imports Spec. Это вполне может стать более актуальным, если либо HTML модули Предложения приняты. Если у вас есть какие-либо идеи для использования случаев, мы увидимся в разделе комментариев.

Жизненный цикл страницы

Таким образом, ваша страница жизненного цикла может выглядеть что-то подобное:

  1. Получить критические ресурсы, в том числе полифилл
  2. Построить домо
  3. Fetch отсроченные сценарии и модули, в том числе lazy-image.js.
  4. Domcontentloaded – Документ закончен разбор
  5. Polyfills Finish Setup, WebComponents.waitfor вызывает свой обратный вызов
  6. Пользовательские элементы обновлены – каждый экземпляр В документе обновляется до пользовательского элемента. Конструктор и ConnectedCallback запустить.
  7. Если JavaScript создает экземпляр Конструктор будет работать. Когда экземпляр подключен к дереву DOM, ConnectedCallback будет работать.
  8. Если JavaScript удаляет экземпляр от DOM, Отключенcallback будет работать.

Ленивая загрузка

Мы будем использовать ПересечениеОбсеревер API для ленивой загрузки. Когда изображение пересекается с прямоугольником, немного больше, чем на экране, мы начнем загрузку, и, надеюсь, он будет полностью загружен тем временем, когда изображение прокручивается на вид. ConnectedCallback так же хорошее место, как любое, чтобы сделать эту работу.

Во-первых, давайте определим быстрый предикат у корня объема нашего модуля:

// isIntersecting :: IntersectionObserverEntry -> Boolean
const isIntersecting = ({isIntersecting}) => isIntersecting

Тогда мы можем настроить наблюдатель, когда наши элементы инстанции:

constructor() {
  super();
  // Bind the observerCallback so it can access the element with `this`.
  this.observerCallback = this.observerCallback.bind(this);
}

connectedCallback() {
  // initialize pre-upgrade attributes
  this.src = this.getAttribute('src')
  this.alt = this.getAttribute('alt')
  // Set up shadow root.
  if (!this.shadowRoot) {
    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this.shadowImage = this.shadowRoot.getElementById('image');
  }
  // If IntersectionObserver is available, initialize it.
  // otherwise, simply load the image.
  if ('IntersectionObserver' in window) this.initIntersectionObserver()
  else this.intersecting = true
}

/**
 * Sets the `intersecting` property when the element is on screen.
 * @param  {[IntersectionObserverEntry]} entries
 * @protected
 */
observerCallback(entries) {
  // The observer simply sets a property
  if (entries.some(isIntersecting)) this.intersecting = true
}

/**
 * Initializes the IntersectionObserver when the element instantiates.
 * @protected
 */
initIntersectionObserver() {
  if (this.observer) return;
  // Start loading the image 10px before it appears on screen
  const rootMargin = '10px';
  this.observer =
    new IntersectionObserver(this.observerCallback, { rootMargin });
  this.observer.observe(this);
}

Когда наблюдатель триггеры и устанавливают пересекание Свойство, давайте отражем его как атрибут и начните загружать изображение. Поскольку этот наблюдатель должен только один раз, мы можем отключить и разгрузить его, как только это сделано.

/**
 * Whether the element is on screen.
 * @type {Boolean}
 */
set intersecting(value) {
  if (value) {
    this.shadowImage.src = this.src;
    this.setAttribute('intersecting', '');
    this.disconnectObserver();
  } else {
    this.removeAttribute('intersecting')
  }
}

get intersecting() {
  return this.hasAttribute('intersecting')
}

/**
 * Disconnects and unloads the IntersectionObserver.
 * @protected
 */
disconnectObserver() {
  this.observer.disconnect();
  this.observer = null;
  delete this.observer;
}

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

disconnectedCallback() {
  this.disconnectObserver()
}

Укладка нашего компонента

Теперь у нас достаточно, чтобы лениво загрузить наше изображение, как только он появится на экране, но мы хотим, например, наш элемент также предоставить ux ux , например, загрузки изображения зажигания заполнения. Для этого мы стимулируем наш компонент, добавив <СТИЛЬ> тег в тень нашего элемента.

const tagName = 'lazy-image';
const template = document.createElement('template');
template.innerHTML = `
  

  
`; window.ShadyCSS && window.ShadyCSS.prepareTemplate(template, tagName);

: хост и <слот>

Оооо! Новый Груз ! : хозяин Селектор CSS сам элемент. Это не просто псевдоэлемент, но и функция, как мы видим с : хозяин ([пересекание]) который эквивалентен ленивое изображение [пересекание] , если он был выбран из-за пределов теневого корня.

<Слот> Элемент, и это связано :: leded () Функция CSS – это части спецификации, которые давайте передаем биты DOM от светового дерева в теневое дерево. Вы используете <Слот> Внутри тени, как мы видели чуть выше. Тогда вы передаете содержимое от света, как теневое дерево, как так:



  
    
      
    
  



  

Уведомление здесь, как мы имели в виду Ограничения полифилл и завернул наши <Слот> в оформлении

затем выбран для детей этого
в наших CSS.

<Слот> На самом деле на самом деле не перемещается или не добавляет щелевые элементы, он просто отображает их, как будто они были в корне тени. Таким образом, стили, которые применяются к удаленным содержанием от внешнего документа, все равно будут применяться при прорезве. Ваш элемент может добавить свои собственные стили, чтобы подрезать контент с помощью :: leded () CSS функция.

::slotted(svg) {
  /* applies to any slotted svg element */
}

::slotted(img) {
  /* applies to any slotted img element */
}

Обратите внимание хорошо : :: leded (*) Выбирает для Только элементы , не текстовые узлы. Это также выбирает только для узлов верхнего уровня, а не детей:

/* Don't do this */
.wrapper ::slotted(.outer .inner) { /*...*/ }
.wrapper ::slotted(.inner) { /*...*/ }

/* Do this */
.wrapper ::slotted(.outer) { /*...*/ }

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

Слоты могут быть названы или анонимными. Назовите слоту, давая ему name = "slotname" атрибут в Shadow DOM и используйте его, указав

в светом доме. Именные слоты полезны, если вы хотите предоставить несколько конкретных настраиваемых функций. В нашем случае мы используем название <Слот> ради яплицерации, но мы могли бы так же легко использовать анонимную <Слот> Отказ




I'm the article title

I'm the article content

I get slotted into the anonymous slot, too

Теперь, когда мы передали нашу светлое заполнителю Дом в нашу тени, давайте обновим методы нашего класса для обработки заполнителя:

set intersecting(value) {
  if (value) {
    // Wait to apply the `intersecting` attribute until the image
    // finishes loading, then update the styles for polyfill browsers
    this.shadowImage.onload = this.setIntersecting;
    this.shadowImage.src = this.src;
    this.disconnectObserver();
  } else {
    this.removeAttribute('intersecting');
  }
}

constructor() {
  super();
  this.setIntersecting = this.setIntersecting.bind(this);
}

/**
 * Sets the intersecting attribute and reload styles if the polyfill is at play.
 * @protected
 */
setIntersecting() {
  this.setAttribute('intersecting', '');
  this.updateShadyStyles();
}

connectedCallback() {
  this.updateShadyStyles();
  /* etc. */
}

/**
 * When the polyfill is at play, ensure that styles are updated.
 * @protected
 */
updateShadyStyles() {
  window.ShadyCSS && window.ShadyCSS.styleElement(this);
}

Проверьте пример в https://glitch.me/abalone-mongoose

😎 Ницца! Наш автономный, многоразовый, одноразовый пользовательский элемент настраивает изображение, когда на экране затем исчезает к нему из щелевого заполнителя.

<В отставке> Кстати, это отличная возможность увидеть, как разбиваются полифиллики близко. Если вы загружаете эту страницу в поддерживающем браузере, вы увидите тег в стиле в теневом дереве элемента, но если вы загружаете его на полифиллированном браузере, как Edge или Firefox 62, вы не увидите стилей, потому что Sadycss Polyfill Поднимает стили тени до головы документа.

CSS пользовательские свойства

Shadow DOM поддерживает наши стили, выделенные от остальной части документа, но это означает, что для наших пользователей сложнее настраивать наш компонент. Счастливых для нас, CSS пользовательские свойства пронзили границу тени, поэтому мы можем использовать их для выставления настраиваемых стилей на наших элементах.

Мы сделаем это, просто определяя наши стили с помощью пользовательских свойств. Синтаксис пользовательских свойств позволяет использовать переменные объявления при назначении значений по умолчанию:

.selector {
  rule: var(--custom-property-name, default);
}

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

#image,
#placeholder ::slotted(*) {
  position: absolute;
  top: 0;
  left: 0;
  transition:
    opacity
    var(--lazy-image-fade-duration, 0.3s)
    var(--lazy-image-fade-easing, ease);
  object-fit: var(--lazy-image-fit, contain);
  width: var(--lazy-image-width, 100%);
  height: var(--lazy-image-height, 100%);
}

Затем мы можем настроить эти стили во всем мире, либо на определенный элемент, определяя эти переменные в наших стилях документа:

/* applies to the whole document. */
html {
  --lazy-image-width: 400px;
  --lazy-image-height: 200px;
}

/* applies to specific elements */
lazy-image:last-of-type {
  width: 400px;
  height: 200px;
  --lazy-image-width: 100%;
  --lazy-image-height: 100%;
  --lazy-image-fade-duration: 2s;
  --lazy-image-fade-easing: linear;
}

Доступность

Прежде чем публиковать наш компонент, давайте убедитесь, что он относится к всем нашим пользователям с уважением. Вы не будете служить вкусным навязанным на гриль-ребрах (кто-то еще голоден?) Без обрезки излишки висит на битах и крутине. Никто не хочет жевать это! Давайте обремем жир от нашего компонента дерево A11Y

Расширение встроенных элементов

Спецификация пользовательских элементов предусматривает Настройка встроенных элементов Отказ Для справки, настроенные встроенные элементы выглядят так:




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

Доступные автономные элементы

Поскольку наша компонент обернет Элемент, вместо того, чтобы расширять его, мы должны попытаться сделать всю нашу оберточную дому прозрачным для считывателей экрана. Сначала мы обновим нашу начальную разметку, чтобы заполнитель был показан деревом A11Y, но не изображение.




Далее мы настроим Презентация Роль, так что обертка нашего элемента игнорируется в пользу его содержимого Screeneaders.

connectedCallback() {
  // Remove the wrapping `` element from the a11y tree.
  this.setAttribute('role', 'presentation');
  /* etc. */
  this.shadowPlaceholder = this.shadowRoot.getElementById('placeholder');
}

И последнее, мы поменяем Aria-Hidden Атрибуты на нашем теневом изображении и заполнятелях после нагрузки изображения.

setIntersecting() {
  /* etc. */
  this.shadowImage.setAttribute('aria-hidden', 'false')
  this.shadowPlaceholder.setAttribute('aria-hidden', 'true')
}

Теперь наше дерево A11Y – это приятный и аккуратный, пользователи нашего экрана читателя не будут беспокоиться с посторонним домом.

Скриншот дерева доступности, показывающий одну кнопку и две графики

Проверьте пример в https://glitch.me/cream-art

Убийца. Вот наш полный модуль:

const isIntersecting = ({isIntersecting}) => isIntersecting;

const tagName = 'lazy-image';
const template = document.createElement('template');
template.innerHTML = `
  
  
`; window.ShadyCSS && window.ShadyCSS.prepareTemplate(template, tagName); class LazyImage extends HTMLElement { /** * Guards against loops when reflecting observed attributes. * @param {String} name Attribute name * @param {any} value * @protected */ safeSetAttribute(name, value) { if (this.getAttribute(name) !== value) this.setAttribute(name, value); } static get observedAttributes() { return ['src', 'alt']; } /** * Image URI. * @type {String} */ set src(value) { this.safeSetAttribute('src', value); if (this.shadowImage && this.intersecting) this.shadowImage.src = value; } get src() { return this.getAttribute('src'); } /** * Image alt-text. * @type {String} */ set alt(value) { this.safeSetAttribute('alt', value); if (this.shadowImage) this.shadowImage.alt = value; } get alt() { return this.getAttribute('alt'); } set intersecting(value) { if (value) { this.shadowImage.onload = this.setIntersecting; this.shadowImage.src = this.src; this.disconnectObserver(); } else { this.removeAttribute('intersecting'); } } /** * Whether the element is on screen. * @type {Boolean} */ get intersecting() { return this.hasAttribute('intersecting'); } constructor() { super(); this.observerCallback = this.observerCallback.bind(this); this.setIntersecting = this.setIntersecting.bind(this); } connectedCallback() { this.setAttribute('role', 'presentation'); this.updateShadyStyles(); if (!this.shadowRoot) { this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(template.content.cloneNode(true)); this.shadowImage = this.shadowRoot.getElementById('image'); this.shadowPlaceholder = this.shadowRoot.getElementById('placeholder'); this.src = this.getAttribute('src'); this.alt = this.getAttribute('alt'); this.placeholder = this.getAttribute('placeholder'); } if ('IntersectionObserver' in window) this.initIntersectionObserver(); else this.intersecting = true; } attributeChangedCallback(name, oldVal, newVal) { this[name] = newVal; } disconnectedCallback() { this.disconnectObserver(); } /** * When the polyfill is at play, ensure that styles are updated. * @protected */ updateShadyStyles() { window.ShadyCSS && window.ShadyCSS.styleElement(this); } /** * Sets the intersecting attribute and reload styles if the polyfill is at play. * @protected */ setIntersecting(event) { this.shadowImage.removeAttribute('aria-hidden'); this.shadowPlaceholder.setAttribute('aria-hidden', 'true'); this.setAttribute('intersecting', ''); this.updateShadyStyles(); } /** * Sets the `intersecting` property when the element is on screen. * @param {[IntersectionObserverEntry]} entries * @protected */ observerCallback(entries) { if (entries.some(isIntersecting)) this.intersecting = true; } /** * Initializes the IntersectionObserver when the element instantiates. * @protected */ initIntersectionObserver() { if (this.observer) return; // Start loading the image 10px before it appears on screen const rootMargin = '10px'; this.observer = new IntersectionObserver(this.observerCallback, { rootMargin }); this.observer.observe(this); } /** * Disconnects and unloads the IntersectionObserver. * @protected */ disconnectObserver() { this.observer.disconnect(); this.observer = null; delete this.observer; } } const register = () => customElements.define(tagName, LazyImage); window.WebComponents ? window.WebComponents.waitFor(register) : register();

Вы можете использовать В ваших проектах, установив из NPM или погрузка от Unpkg Отказ

npm i -S @power-elements/lazy-image

Взносы Добро пожаловать на Github Отказ

Выводы

Мы достигли нашей цели на написание Slick, многоразовая, доступная, без зависимостей, одно файловый компонент загрузки беззаконного загрузки. И это всего 1,94 КБ сжатый, 4,50 кБ. Что мы узнали?

Ванильные компоненты плюсы и минусы

Вам нужно будет предоставить свои собственные помощники. Синхронизация свойств с атрибутами может стать громоздкой. Никаких зависимостей не требуется. Ваш код является будущим доказательством, потому что он опирается на веб-стандарты вместо библиотеки Churn.
Компоненты 0-DEP не используют библиотеки Mixins или Helper для уменьшения файлов в крупных проектах. Небольшой след загрузки, поскольку не требуются дополнительные круглые переговоры для библиотечного кода
Низкоуровневые веб-примитивы иногда могут быть громоздкими. Нет нестандартных API для изучения, обслуживания или адаптации к. Это просто в Интернете.
Вы должны выйти из своего пути для поддержки браузеров Polyfill, тогда как с библиотеками, ограничениями полифилл и известные проблемы абстрагированы. Низкоуровневая мощность дает вам контроль и гибкость. Вы можете учитывать ваши компоненты, однако вы хотите.

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

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

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

Увидимся тогда 🕵️♂️🕵️♀️

Хотели бы вы сеанс наставничества на одну на одну на одну из темов, покрытых здесь?

Свяжитесь со мной на кода

Признательности

Спасибо без особых приказа Джоне Пакеру, Уэстбрулу Джонсону, Гофферту Ван Гулю, Мэтту Гаварецким и Даниэлем Тернер для их предложений и исправлений.

Ошибка