Как сделать lazyload совместимым с любым браузером + Placeholder


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

Внизу страницы приведен полный код того, как сделать эффект lazyload.

Но после того, как вы выполнили задание Jira, я настоятельно рекомендую вам вернуться и прочитать все до конца, чтобы все понять.

Если вы здесь, значит, вы уже знаете, чего хотите: создать эффект ленивой нагрузки.

Но если вы все еще сомневаетесь в том, как работает lazyload, я предлагаю вам прочитать введение в моем последнем посте. Там я говорил об атрибуте loading=»lazy» и о том, как осторожно нужно с ним обращаться.

Что такое lazyload?

Короче говоря, lazyload задерживает загрузку тяжелых ресурсов.

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

В упомянутом посте я также прокомментировал преимущества lazyload, такие как производительность, SEO и потребление полосы пропускания.

Но давайте перейдем к делу.

Как сделать эффект lazyload: атрибуты src и data-src

Понимание этих атрибутов жизненно важно для создания ленивой нагрузки.

Как вы уже догадались, атрибут src сообщает браузеру источник файла изображения. И именно через него браузер загружает этот файл. Но что, если атрибут src не существует?

<img alt="Sweet cat" class="image" />
Войдите в полноэкранный режим Выход из полноэкранного режима

Все происходит именно так, как я хочу: браузер ничего не загружает, и страница загружается быстрее.

В любом случае, я должен указать браузеру, откуда загрузить изображение. Для этого я создаю data-src:

<img
  data-src="cat.png"
  alt="Sweet cat"
  class="image"
/>
Войдите в полноэкранный режим Выход из полноэкранного режима

Поскольку data-src не имеет собственного поведения в браузере, он будет служить только в качестве «базы данных». То есть, он будет хранить URL изображения. Пока что.

Очень важно, чтобы размер изображения был объявлен с помощью атрибутов width и height. В противном случае изображение будет иметь высоту 0, что приведет к проблемам в дальнейшем.

<img
  data-src="cat.png"
  alt="Sweet cat"
  width="400"
  height="400"
  class="image"
/>
Войдите в полноэкранный режим Выход из полноэкранного режима

Таким образом, вы предотвратите страшное явление кумулятивного сдвига макета (CLS). Это когда элементы вашей страницы меняются местами в процессе разработки других элементов. Видео в начале этой страницы о CLS показывает, насколько серьезна эта проблема.


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

Как сделать эффект Lazyload: загрузка изображения

Есть два способа сделать это (я буду использовать Javascript в обоих случаях):

  1. Событие прокрутки
  2. Наблюдатель на перекрестке

Наблюдение за изображениями через событие прокрутки

Я создам две константы:

const images = Array.from(document.querySelectorAll('.image[data-src]'));
const screenHeight = window.innerHeight;
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь я собираюсь отслеживать событие прокрутки окна:

window.addEventListener('scroll', checkNotLoadedImages);
Войдите в полноэкранный режим Выход из полноэкранного режима

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

function checkNotLoadedImages() {
  const notLoadedImages = images.filter(image => !image.src);

  notLoadedImages.forEach(image => {
    const imageTop = image.getBoundingClientRect().top;

    if (imageTop < screenHeight) {
      image.src = image.dataset.src; // é aqui que a magia acontece

      // Você pode até remover o atributo data-src, mas não é tão necessário
      image.removeAttribute('data-src');
    }
  });
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Каждый раз, когда пользователь использует полосу прокрутки:

  • Я фильтрую список изображений и беру только те, которые еще не были загружены
  • Затем я выполняю итерации по этому новому списку с помощью forEach. Для каждого выгруженного изображения я создаю константу imageTop. Ему присваивается число, которое является расстоянием в пикселях от верхней части изображения до верхнего типа экрана
  • Если это значение меньше высоты экрана (screenHeight), это означает, что он виден. Браузер, я выбираю тебя: скачать изображение

Изображение загружается в тот момент, когда атрибут src заполняется значением, которое находится в date-src:

image.src = image.dataset.src;
Войдите в полноэкранный режим Выход из полноэкранного режима

В этом тесте на вкладке «Сеть» видно, что изображения загружаются только тогда, когда они попадают в область просмотра:

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

Совет по работе с видовыми экранами:

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

Для этого увеличьте область активации изображения с помощью смещения. Пример:

const offset = 500;
const screenHeight = window.innerHeight + offset;
Войдите в полноэкранный режим Выход из полноэкранного режима

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

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

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

Мониторинг изображений с помощью Intersection Observer

Пересечение чего?!

Наблюдатель пересечений (MDN) позволяет наблюдать за элементом, а не следить за прокруткой, как раньше. Когда он пересекается с родительским элементом или областью просмотра, можно выполнить функцию обратного вызова.

Точно так же, как я сделал в примере с событием прокрутки, но каждое изображение просматривается отдельно. Это позволяет убрать forEach, который продолжает «искать» изображения при каждой прокрутке.

Я начну с того, что вы уже знаете:

const offset = 500;
const images = Array.from(document.querySelectorAll('.image[data-src]'));
Войдите в полноэкранный режим Выход из полноэкранного режима

Нет ничего нового под солнцем.

Я собираюсь создать observer и передать ему два параметра:

const observer = new IntersectionObserver(checkEntries, intersectionOptions);
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь рассмотрите каждый из них в деталях:

function checkEntries(entries) {
  entries.forEach(entry => {
    if (entry.isIntersecting) showImage(entry.target);
  })
}
Войдите в полноэкранный режим Выход из полноэкранного режима

При каждом появлении изображения на экране эта функция будет выполняться.

Если у записи (entry) значение атрибута isIntersecting равно true, то она видна в области просмотра. В этот момент я выполняю функцию showImage() и в качестве параметра ввожу HTML-элемент, который находится по адресу entry.target.

const intersectionOptions = {
  root: null,
  rootMargin: offset + 'px',
  threshold: 0
};
Войдите в полноэкранный режим Выход из полноэкранного режима

Конечно же, функция showImage():

function showImage(image) {
  image.src = image.dataset.src; // a magia novamente
  observer.unobserve(image);
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

Теперь я буду фактически «наблюдать» за каждым изображением:

images.forEach(image => observer.observe(image));
Войдите в полноэкранный режим Выход из полноэкранного режима

Если вы повторите тест сетевой вкладки, вы увидите, что созданный эффект lazyload по-прежнему работает нормально.


Пока можно сказать, что все закончилось.

Но что, если даже со смещением интернет тормозит и изображение не загружается? Пользователь увидит пустую часть экрана?

Чтобы обойти это элегантным способом, я создам заполнитель.

Как сделать эффект Lazyload: placeholder

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

Placeholder — это то, что занимает место изображения до его загрузки.

Существуют сотни типов заполнителей, но здесь я остановлюсь на хорошо известном типе под названием Skeleton Placeholder:

Вот общее объяснение того, как работает это место:

  • Изображение будет размещено внутри тега <picture>.
  • Этот тег будет иметь те же размеры, что и изображение
  • Он также получит некоторые стили, например, цвет фона и анимацию в CSS.
  • Когда изображение будет загружено, оно займет место заполнителя

HTML для размещения

Здесь все очень просто.

Скорректируйте HTML и поместите изображения внутри &lt;picture&gt;:

<!-- ANTES -->
<img
  data-src="https://placekitten.com/400/400"
  width="400"
  height="400"
  class="image"
/>

<!-- DEPOIS -->
<picture class="picture lazyload-not-loaded">
  <img
    data-src="https://placekitten.com/400/400"
    width="400"
    height="400"
    class="image"
  />
</picture>
Войдите в полноэкранный режим Выход из полноэкранного режима

Placeholder CSS

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

.picture {
  display: inline-block;
  overflow: hidden;
}

.picture .image {
  display: block;
  max-width: 100%;
  height: auto;
  transition: .3s;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Это позволит ему иметь тот же размер, что и изображение, а также адаптировать изображение к нему.

Теперь посмотрите, как будет выглядеть подложка, пока изображение не загрузилось. Для этого я буду использовать класс lazyload-not-loaded:

.picture.lazyload-not-loaded {
  position: relative;
  background-color: lightgray;
}

.picture.lazyload-not-loaded .image {
  opacity: 0;
}

.picture.lazyload-not-loaded::before,
.picture.lazyload-not-loaded::after {
  content: '';
  position: absolute;
  top: 0;
  left: -400%;
  width: 400%;
  height: 100%;
  animation-name: loading;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
  background-image: linear-gradient(135deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0) 30%,
    rgba(255, 255, 255, 1) 50%,
    rgba(255, 255, 255, 0) 70%,
    rgba(255, 255, 255, 0) 100%
  );
}

.picture.lazyload-not-loaded::after {
  animation-delay: 1.5s;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Что здесь было сделано?

  • Элемент &lt;picture&gt; имеет серый цвет фона
  • Изображение имеет нулевую непрозрачность
  • ::after и ::before элемента &lt;picture&gt; составят ту белую линию, которая переходит на заполнитель.
  • ::after имеет задержку, чтобы она не произошла одновременно с ::before.

И результат таков:

Код для анимации псевдоэлементов довольно прост:

@keyframes loading {
  from { left: -400%; }
  to   { left: 0;     }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

И наконец, когда изображение наконец загружается, оно появляется с переходом на opacity:

.picture.lazyload-loaded .image {
  opacity: 1;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь, когда CSS готов, мне нужно управлять поведением классов, чтобы заставить lazyload действительно работать.

Класс lazyload-not-loaded будет вставлен в <picture> непосредственно в HTML, потому что именно так загружается страница. Сразу после загрузки изображения класс будет заменен на lazyload-loaded с помощью JavaScript.

Placeholder Javascript

Настройка должна быть выполнена только в функции showImage():

function showImage(image) {
  const picture = image.parentNode; // aqui
  picture.classList.remove('lazyload-not-loaded'); // aqui
  picture.classList.add('lazyload-loaded'); // e aqui
  image.src = image.dataset.src;
  observer.unobserve(image);
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

function showImage(image) {
  setTimeout(() => {
    const picture = image.parentNode;
    picture.classList.remove('lazyload-not-loaded');
    picture.classList.add('lazyload-loaded');
    image.src = image.dataset.src;
    observer.unobserve(image);
  }, 2000);
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

  • Измените селектор .picture.lazy-loaded-not-loaded .image на изображение до его загрузки
  • И селектор .picture.lazyload-loaded .image после его загрузки

Все это объяснение было довольно обширным, я знаю.

Поэтому я решил сгруппировать (но замаскировать) весь код ниже. Таким образом, вам будет проще адаптировать его к своему проекту.

Пример кода Lazyload

«Вот то, что вы хотели.

Наслаждайтесь этим замечательным кодексом, который я напечатал на доске в своем офисе (шутка).

Но это может быть серьезно.

Но это шутка.

<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/400/400" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/401/401" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/402/402" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/403/403" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/404/404" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/405/405" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/406/406" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/407/407" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/408/408" width="400" height="400" class="image"></picture>
<picture class="picture lazyload-not-loaded"><img data-src="https://placekitten.com/409/409" width="400" height="400" class="image"></picture>

Войдите в полноэкранный режим Выход из полноэкранного режима
@keyframes loading {
  from { left: -400%; }
  to   { left: 0;     }
}

.picture {
  display: inline-block;
  overflow: hidden;
}

.picture .image {
  display: block;
  max-width: 100%;
  height: auto;
  transition: .3s;
}

.picture.lazyload-not-loaded {
  position: relative;
  background-color: lightgray;
}

.picture.lazyload-not-loaded .image {
  opacity: 0;
}

.picture.lazyload-not-loaded::before,
.picture.lazyload-not-loaded::after {
  content: '';
  position: absolute;
  top: 0;
  left: -400%;
  width: 400%;
  height: 100%;
  animation-name: loading;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
  background-image: linear-gradient(135deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0) 30%,
    rgba(255, 255, 255, 1) 50%,
    rgba(255, 255, 255, 0) 70%,
    rgba(255, 255, 255, 0) 100%
  );
}

.picture.lazyload-not-loaded::after {
  animation-delay: 1.5s;
}

.picture.lazyload-loaded .image {
  opacity: 1;
}
Войдите в полноэкранный режим Выход из полноэкранного режима
/* SCROLL EVENT */

/*
const images = Array.from(document.querySelectorAll('.image[data-src]'));
const offset = 500;
const screenHeight = window.innerHeight + offset;

window.addEventListener('scroll', checkNotLoadedImages);

function checkNotLoadedImages() {
  const notLoadedImages = images.filter(image => !image.src);

  notLoadedImages.forEach(image => {
    const imageTop = image.getBoundingClientRect().top;

    if (imageTop < screenHeight) {
      image.src = image.dataset.src; // é aqui que a magia acontece
    }
  });
}
*/

/* INTERSECTION OBSERVER */

const offset = 500;
const images = Array.from(document.querySelectorAll('.image[data-src]'));

const intersectionOptions = {
  root: null,
  rootMargin: offset + 'px',
  threshold: 0
};

function checkEntries(entries) {
  entries.forEach(entry => {
    if (entry.isIntersecting) showImage(entry.target);
  })
}

const observer = new IntersectionObserver(checkEntries, intersectionOptions);

function showImage(image) {
  const picture = image.parentNode;
  picture.classList.remove('lazyload-not-loaded');
  picture.classList.add('lazyload-loaded');
  image.src = image.dataset.src;
  observer.unobserve(image);
}

images.forEach(image => observer.observe(image));
Войдите в полноэкранный режим Выход из полноэкранного режима

Обратный звонок

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

  1. Удалите атрибут src из изображения, чтобы оно не загружалось вместе с другими элементами
  2. Установите время, в которое вы хотите загрузить его, это можно сделать с помощью события прокрутки или наблюдателя Intersection Observer

А чтобы завершить глазурь на торте, вы можете поместить стильный держатель.

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

  • В некоторых случаях тег <source> используется внутри тега <picture>, чтобы обеспечить правильное изображение для разных размеров экрана. Эти элементы также нуждаются в ленивой загрузке. Затем вам нужно будет изменить функцию showImage() для обработки атрибутов srcset и date-srcset.
  • Еще одна идея, которая мне очень нравится, — использовать низкокачественное изображение в качестве подложки. Для этого нужно оставить атрибут src в теге <img>, но с адресом изображения низкого качества. Размер этих изображений составляет около 1 кб или меньше, поэтому они загружаются очень быстро и заменяются оригинальным изображением сразу после загрузки.

Здесь небо — это предел.

Я намерен вернуться к этим идеям в будущих статьях.

Помог ли я вам чем-нибудь? Тогда оставьте «спасибо» в комментариях 😉

Спасибо, что читаете!

Оцените статью
devanswers.ru
Добавить комментарий