Создание своего прогрессивного загрузчика изображений

Создание своего прогрессивного загрузчика изображений

От автора: вы могли видеть прогрессивные изображения на Facebook и Medium. Замыленные изображения низкого качества заменяются на полноразмерные версии, когда элемент попадает в поле зрения.

Создание своего прогрессивного загрузчика изображений

Превью изображение крошечное. Возможно, сильно сжатый JPEG, 20px в ширину. Файл может весить меньше 300 байт, он появляется мгновенно, придавая ощущение быстрой загрузки. Реальное изображение подгружается по мере необходимости с помощью метода ленивой загрузки.

Прогрессивные изображения это хорошо, но текущие решения немного сложны. К счастью, можно создать свое на HTML5, CSS3 и JS. Код будет:

быстрым и легким – всего 463 байта CSS и 1 007 байта JS (в минифицированном виде);

Современные тенденции и подходы в веб-разработке

Узнайте алгоритм быстрого профессионального роста с нуля в сайтостроении

Узнать подробнее

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

без зависимостей – будет работать со всеми фреймворками;

работать во всех современных браузерах (IE10+);

работать в старых браузерах по технике прогрессивного улучшения, или когда JS или загрузка изображений отказали;

легким в использовании.

Наше демо и код на GitHub

Вот так выглядит наша техника:

Скачать код с GitHub

HTML

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

<a href="full.jpg" class="progressive replace">
  <img src="tiny.jpg" class="preview" alt="image" />
</a>

Где:

full.jpg – полноразмерное изображение, хранящееся в href, а

tiny.jpg – наше маленькое превью.

Мы получили минимально работающую систему. Без JS (или в старых браузерах с отключенным JS) пользователь может просмотреть полноразмерное изображение по клику на превью.

У обоих изображений должно быть одинаковое соотношение сторон. Например, если full.jpg имеет размеры 800 х 200, то соотношение сторон составляет 4:1. Превью tiny.jpg должно быть 20 х 5. Нельзя поставить ширину 30px, так как тогда нужна будет невозможная высота 7.5px.

Обратите внимание на классы на ссылке и превью изображении. Это будут наши хуки в JS.

Инлайнить или не инлайнить изображения

Превью изображение можно заинлайнить в виде data URI.

<img src="data:image/jpeg;base64,/9j/4AAQSkZJ..."  class="preview" />

Инлайновые изображения появляются мгновенно, требуют меньше HTTP запросов и лишний раз не перестраивают страницу. Однако:

инлайновое изображение намного сложнее вставить и изменить (может помочь Gulp);

кодировка base-64 менее эффективна и на 30% больше, чем бинарные файлы (хотя вес компенсируется дополнительными заголовками HTTP запросов);

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

HTTP/2 убирает необходимость в инлайновых изображениях.

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

CSS

Начнем со стилей контейнера для ссылок:

a.progressive {
  position: relative;
  display: block;
  overflow: hidden;
  outline: none;
}

В коде выше заданы основные свойства макета. При необходимости к ссылке могут применяться другие стили и классы для установки размеров и позиции.

Можно попробовать задать точные размеры или выставить соотношение сторон через padding-top. Так контейнер получит размеры до загрузки изображения, что избавит нас от перестройки страницы. Нужно будет вычислить размеры и/или соотношения ширины к высоте всех изображений. Я не стал все усложнять:

превью и большое изображения должны иметь одинаковое соотношение сторон (см. выше);

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

Современные тенденции и подходы в веб-разработке

Узнайте алгоритм быстрого профессионального роста с нуля в сайтостроении

Узнать подробнее

И снова, будьте прагматичны: если задать ширину и высоту контейнера, производительность поднимется, но это будет особенно заметно на страницах с большим количеством изображений, например, галерея (где у всех изображений одинаковое соотношение сторон).

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

a.progressive:not(.replace) {
  cursor: default;
}

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

a.progressive img {
  display: block;
  width: 100%;
  max-width: none;
  height: auto;
  border: 0 none;
}

Свойство height: auto обязательно, так как IE10/11 может ошибиться с высотой изображений.

Превью размыто со значением 2vw. Так размытие кажется одинаковым независимо от размеров страницы. Свойство overflow: hidden на контейнере придает изображению четкий край. Превью увеличено на 1.05, чтобы не был виден задний фон. Это значит, что для появления полного изображения можно использовать приятный эффект зума.

a.progressive img.preview {
  filter: blur(2vw);
  transform: scale(1.05);
}

В конце добавляет стили и анимацию для полного изображения при появлении:

a.progressive img.reveal {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform, opacity;
  animation: reveal 1s ease-out;
}

@keyframes reveal {
  0% {transform: scale(1.05); opacity: 0;}
  100% {transform: scale(1); opacity: 1;}
}

Полное изображение помещается поверх превью, после чего opacity увеличивается с 0 до 1, а масштаб меняется с 1.05 на 1 за одну секунду. По желанию можно использовать другие трансформации и/или фильтры.

JavaScript

Сейчас мы попрактикуемся адаптивной технике прогрессивного улучшения. JS код будет сразу проверять доступные браузерные API перед добавлением обработчика события load на страницу:

// progressive-image.js
if (window.addEventListener && window.requestAnimationFrame && document.getElementsByClassName) window.addEventListener('load', function() {

Событие load запускается после полной загрузки страницы и всех файлов. Нам не нужно, чтобы большие изображения начали загружаться до того, как будут загружены необходимые ресурсы типа шрифтов, CSS, JS и превью изображений (что могло случиться, если бы мы использовали события DOMContentLoaded, запускающиеся по готовности DOM).

Затем мы получаем все изображения в контейнере с классами progressive и replace:

var pItem = document.getElementsByClassName('progressive replace'), timer;

getElementsByClassName() возвращает живую HTMLCollection, похожую на массив, которая меняется при добавлении и удалении ассоциированных элементов на странице. Вы быстро поймете преимущества коллекции.

Далее мы создаем функцию inView(), которая с помощью сравнивания позиции getBoundingClientRect и позиции вертикального скрола window.pageYOffset будет определять, попал ли каждый контейнер в поле зрения:

// изображение в поле зрения?
function inView() {
  var wT = window.pageYOffset, wB = wT + window.innerHeight, cRect, pT, pB, p = 0;
  while (p < pItem.length) {

 cRect = pItem[p].getBoundingClientRect();
 pT = wT + cRect.top;
 pB = pT + cRect.height;

 if (wT < pB && wB > pT) {
 loadFullImage(pItem[p]);
 pItem[p].classList.remove('replace');
 }
 else p++;
  }
}

Когда контейнер в поле зрения, его узел передается в функцию loadFullImage(), а класс replace удаляется. Это мгновенно удаляет узел с pItem HTMLCollection, чтобы контейнер не обрабатывался повторно.

Функция loadFullImage() создает новый HTML объект Image() и по необходимости задает его значения, т.е. копирует href контейнера в src и добавляет класс reveal:

// замена на полное изображение
function loadFullImage(item) {
  if (!item || !item.href) return;

  // загрузка изображения
  var img = new Image();
  if (item.dataset) {
 img.srcset = item.dataset.srcset || '';
 img.sizes = item.dataset.sizes || '';
  }
  img.src = item.href;
  img.className = 'reveal';
  if (img.complete) addImg();
  else img.onload = addImg;

Внутренняя функция addImg вызывается после загрузки изображения:

// замена изображения
  function addImg() {
 // отключение клика
 item.addEventListener('click', function(e) { e.preventDefault(); }, false);

 // добавление полного изображения
 item.appendChild(img).addEventListener('animationend', function(e) {
 // удаление превью
 var pImg = item.querySelector && item.querySelector('img.preview');
 
 if (pImg) {
 e.target.alt = pImg.alt || '';
 item.removeChild(pImg);
 e.target.classList.remove('reveal');
 }
 });
  }
}

Этот код:

отключает событие click на контейнере;

вставляет изображения на страницу, что запускает плавное появление и зум;

дожидается конца анимации с помощью обработчика animationend, после чего копирует тег alt, удаляет узел с превью и удаляет класс reveal с полного изображения. Этот шаг повышает производительность и избавляет от странных проблем при изменении размера окна в Edge.

В конце необходимо вызвать функцию inView(), которая при первом запуске проверит, видны ли на странице контейнеры с прогрессивными изображениями:

inView();

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

window.addEventListener('scroll', scroller, false);
window.addEventListener('resize', scroller, false);

function scroller(e) {
  timer = timer || setTimeout(function() {
 timer = null;
 requestAnimationFrame(inView);
  }, 300);
}

Обратите внимание на вызов requestAnimationFrame, которая запускает inView до перерисовки.

Адаптивные изображения

HTML5 атрибуты srcset и sizes задают несколько изображений под разные размеры и разрешения. Браузер сам выбирает подходящую версию под устройство.

Код сверху поддерживает эту функцию – добавьте атрибуты data-srcset и data-sizes в контейнер со ссылками, например.

<a href="small.jpg"
  data-srcset="small.jpg 800w, large.jpg 1200w"
  data-sizes="100vw"
  class="progressive replace">
  <img src="preview.jpg" class="preview" alt="image" />
</a>

После загрузки код полного изображения будет:

<img src="small.jpg"
 srcset="small.jpg 800w, large.jpg 1200w"
 sizes="100vw"
 alt="image" />

Современные браузеры загружают large.jpg, когда ширина вьюпорта составляет 800px и выше. Старые браузеры и браузеры с маленькой шириной вьюпорта получат small.jpg. Более подробно читайте в как создавать адаптивные изображения с помощью srcset.

Замечания

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

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

Динамическое добавление прогрессивных изображений. Прогрессивные изображения добавляются на страницу через JS и заменяются только по событиям scroll и resize.

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

Автор: Craig Buckler

Источник: https://www.sitepoint.com/

Редакция: Команда webformyself.

Современные тенденции и подходы в веб-разработке

Узнайте алгоритм быстрого профессионального роста с нуля в сайтостроении

Узнать подробнее
Самые свежие новости IT и веб-разработки на нашем Telegram-канале

Курс по Gulp. Основы

Прямо сейчас посмотрите курс по Gulp!

Смотреть курс

Метки:

Похожие статьи:

Комментарии Вконтакте:

Комментарии Facebook:

Комментарии (1)

  1. vsenatorrent

    с такими названиями переменных далеко не уедешь

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree