Веб-компоненты — ленивая загрузка изображений с помощью Intersection Observer

В предыдущем посте Введение в веб-компоненты мы рассмотрели основы веб-компонентов и создали один автономный веб-компонент.
Теперь попробуем настроить существующий HTML-элемент и придайте ему новый вкус. Мы попытаемся создать настраиваемый элемент изображения <img> . Давайте попробуем добавить функцию ленивой загрузки к существующему элементу HTML <img>. Наш элемент <img> будет откладывать загрузку с нужного src до тех пор, пока он не станет видимым в окне просмотра. Проще говоря, элемент не будет загружать желаемый контент при загрузке страницы, а только тогда, когда он виден на экране.

Техника

Техника, используемая для ленивой загрузки изображения, состоит в том, что фиктивное изображение или изображение-заполнитель загружается при загрузке страницы. Нужное изображение загружается только тогда, когда элемент виден в окне просмотра. Для этого мы должны цепляться за обработчики событий scroll или resize. Хотя это прекрасно работает, современные браузеры предлагают более эффективные и эффективные способы определения того, находится ли элемент в области просмотра с помощью Intersection Observer API.

API-интерфейс наблюдателя за пересечением

Intersection Observer API предоставляет способ асинхронно наблюдать за изменениями в пересечении целевого элемента с элементом-предком или с областью просмотра документа верхнего уровня. Его можно использовать в различных местах, таких как отложенная загрузка изображения, анимация, бесконечная прокрутка и т. д.

использование

/* declare the options */
const options = {
  root: null,
  rootMargin: '0px',
  threshold: [0.50]
};
/* create an instance and pass the callback and options */
const observer = new IntersectionObserver(callback, options);
/* finally observe the target element */
observer.observe(document.querySelector('#elem-to-watch-for'));

Параметры наблюдателя перекрестка

  1. root: элемент-предок, используемый в качестве области просмотра для проверки видимости целевого элемента. По умолчанию используется область просмотра браузера, если она не указана или равна нулю.
  2. rootMargin: поле вокруг корня. Можно указать аналогично свойству поля CSS (верхнее, правое, нижнее, левое). Это позволяет вам увеличивать или уменьшать площадь, используемую для пересечений. По умолчанию нули.
  3. threshold: позволяет определить массив пороговых значений intersectionRatio. Ваш обратный вызов будет вызываться каждый раз, когда intersectionRatio пересекает одно из этих значений. Значение по умолчанию для threshold — [0].

Обратный вызов наблюдателя пересечения

Обратный вызов вызывается каждый раз, когда цель достигает порога. Обратный вызов получает список IntersectionObserverEntry объектов и наблюдателя.

const callback = (entries, observer) => {
  // each entry in the entries array corresponds to one observed //target
  entries.forEach(entry => {
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

Итак, давайте воспользуемся Intersection Observer API для создания веб-компонента, который по сути наследуется от HTMLImageElement и помогает нам лениво загружать нужное изображение.

Как объяснялось выше, нам нужно знать URL-адрес изображения-заполнителя и URL-адрес желаемого изображения. Итак, мы запросим у пользователя эти два значения атрибута:

src: (необязательно) укажите URL-адрес изображения-заполнителя для загрузки заранее. (В качестве альтернативы добавьте URL-адрес заполнителя, если значение не указано пользователем).

data-src: (обязательно) укажите URL нужного изображения.

Шаги

  1. Создайте класс (например, LazyImg) и расширьте его из HTMLImageElement.
  2. Определите параметры, которые будут использоваться в Interserction Observer API.
  3. Укажите резервный URL-адрес заполнителя.
  4. Начните наблюдать за элементом <img>
/* customised-img.js */
class LazyImg extends HTMLImageElement {
  constructor() {
    self = super();
   
    // declare Intersection Observer options
    const options = {
      root: null,
      rootMargin: '0px',
      threshold: [0.50] 
      // invoke the callback as soon as the element is 50% visible
    };
    
    // add fallback placeholder url
    if (!(self.getAttribute('src'))) {
      self.setAttribute(
       'src',  
       'https://i.picsum.photos/id/208/20/30.jpg?blur=5');  
    }

    // instantiate the observer
    const observer = new IntersectionObserver(
     self.handleCallback.bind(self),
     options);
    
    // start observing
    observer.observe(self);
  }
}

5. Добавьте функцию обратного вызова

6. Зарегистрируйте веб-компонент.

/* customised-img.js */
handleCallback(entries, observer) {
  entries.forEach((entry, index) => {
    const target = entry.target;
    const dataSrc = target.getAttribute('data-src');
    const src = target.getAttribute('src');
    if (entry.isIntersecting) {
      if (src !== dataSrc) {
        self.loadImage(observer, target, dataSrc);
      }
    }
  });
}
loadImage(observer, target, dataSrc) {
 // put the desired url as the src
 target.setAttribute('src', dataSrc);
 
 // add a class lazy-loaded
 target.setAttribute(
  'class', 
  `${target.getAttribute('class')} lazy-loaded`);
 // unobserve the element
 observer.unobserve(target);
}
/* finally register the web component */
customElements.define('lazy-img', LazyImg, { extends: 'img' });

Теперь наш веб-компонент готов.

<img 
   is="lazy-img" 
   src="placeholder-url"
   data-src="desired-url" />

Давайте задействуем его.

/* index.html */
<body>
 <div id="dashboard">
 <ul id="dashboard-list">
  <li class="item">
   <img 
    is="lazy-img" 
    class="my-image"
    src="https://i.picsum.photos/id/500/20/30.jpg?blur=3"
    data-src="https://i.picsum.photos/id/500/200/300.jpg" 
    alt="my-image"/>
  </li>
  <li class="item">
   <img 
    is="lazy-img" 
    class="my-image"
    src="https://i.picsum.photos/id/501/20/30.jpg?blur=3"
    data-src="https://i.picsum.photos/id/501/200/300.jpg" 
    alt="my-image"/>
 </li>
 <li class="item">
  <img 
    is="lazy-img" 
    class="my-image"
    src="https://i.picsum.photos/id/602/20/30.jpg?blur=3"
    data-src="https://i.picsum.photos/id/602/200/300.jpg" 
   alt="my-image"/>
  </li>
  <li class="item">
   <img 
     is="lazy-img" 
     class="my-image"
     src="https://i.picsum.photos/id/603/20/30.jpg?blur=3"
     data-src="https://i.picsum.photos/id/603/200/300.jpg" 
     alt="my-image"/>
    </li>
   </ul>
 </div>
 <script src="customised-img.js"></script>   
</body>

Вуаля! Оно работает!

Большое спасибо Lorem Picsum за все красивые и настраиваемые изображения на ходу!