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

В течение долгого времени в JavaScript использовалось множество неверных, несемантических, недоступных уловок для выполнения «ленивой загрузки» изображений. Большинству систем требовалась раздутая разметка, они плохо деградировали, часто дважды дублировали информацию об изображении с помощью <noscript> и еще целую кучу других уловок.

Эти уловки и чрезмерное использование сценариев в течение долгого времени заставляли меня уклоняться от подобных эффектов, отвергая их как некорректный / неработающий мусор. Моя реакция коленного рефлекса, когда кто-то просто спросил: «Нет ... тебе ДЕЙСТВИТЕЛЬНО лучше не делать этого».

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

И я обнаружил, что HTML 5 дал нам механизм для этого в виде атрибута loading=”lazy”. Если вы не знакомы с этим, я предлагаю вам прочитать статью MDN по этой теме:



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

Хотя это дает нам базовый механизм с полной постепенной деградацией, на самом деле он не дает особого «контроля» над тем, как изображение выглядит после загрузки. Простое постепенное нарастание, масштабирование или другие подобные анимации можно было бы / можно было бы легко добавить, если бы были какие-либо CSS-хуки на случай, если что-то «загружается» или «загружается», но их в настоящее время не существует ... так что мы вернемся к JavaScript для этого.

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

Обратите внимание: весь CSS здесь предполагает использование сброса.

Шаг 1: базовая реализация

Давайте начнем с простого и просто воспользуемся простой анимацией непрозрачности и масштабирования, чтобы показать их, вместо установленной в браузере по умолчанию «вот они».

HTML: чистый и простой

<span class="lazyImage" style="padding-bottom:75%;">
  <img
    src="images/test.jpg"
    alt="Describe this image, ALT is not optional"
    loading="lazy"
  >
</span>

Гораздо меньше HTML, чем в большинстве реализаций этого, которые я видел.

Я выбрал семантически нейтральный SPAN с классом на нем в качестве оболочки, так как мне не нравится случайное создание собственных тегов. Я знаю, что сейчас это допустимо в HTML 5, и, как и в многих HTML 5, считаю это ошибкой. HTML-теги должны что-то значить для пользовательского агента! Пользовательский агент не воспринимает вымышленные теги как нечто большее, чем SPAN, что устраняет всю причину их разрешения.

🎵 Ненависть - сильное слово, но ты мне очень-очень-очень не нравишься ... 🎵

На указанном промежутке я помещаю style="padding-top:75%", чтобы установить соотношение сторон. Это кажется наиболее быстрым и наиболее целесообразным способом решения этой проблемы, даже несмотря на то, что он нарушает разделение ответственности. В большинстве случаев я выступаю против использования атрибута style, говоря, что:

В 100% случаев вы видите <style> и в 99% случаев style=”” в разметке вы смотрите на невежественный мусор.

Но так же, как я не возражаю против тега стиля, если он добавлен из JS для «CSS в JS» в тех немногих случаях, которые оправданы ... ну ... добро пожаловать в этот 1%. Так же, как ширина / высота для диаграмм, сгенерированных HTML / CSS, или размер шрифта для облаков тегов, это тот случай, когда я хочу посмотреть в другую сторону.

Ни одно хорошее правило не должно применяться без исключений. Даже мой.

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

Изображение внутри <span> - это просто нормальное изображение с «новым» loading=”lazy” на нем.

JavaScript: оставайся стильным

(function() {
  if ('loading' in HTMLImageElement.prototype) {
    var lazyImages = document.querySelectorAll('.lazyImage img');
      
    for (var img of lazyImages) {
      if (!img.complete) {
        img.parentNode.classList.add('lazyImageWaiting');
        img.addEventListener('load', lazyImageLoad, false);
        img.addEventListener('error', lazyImageError, false);
      }
    }
    
    function lazyImageLoad(e) {
      e.currentTarget.parentNode.classList.remove('lazyImageWaiting');
    }
    
    function lazyImageError(e) {
      var parent = e.currentTarget.parentNode;
      parent.classList.remove('lazyImageWaiting');
      parent.classList.add('lazyImageError');
      setTimeout(function() {
        parent.classList.add('lazyImageErrorShow');
      }, 60);
    }
    
  } // if 'loading' supported
  
})();

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

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

Если он не завершен, мы добавляем класс «lazyImageWaiting», чтобы CSS мог стилизовать его: да, он либо загружается, либо ожидает загрузки.

Затем мы добавляем слушателей событий. один - когда он загружен, второй - если возникла проблема с его загрузкой. Обработчики событий, когда он завершен, просто извлекают класс lazyImageWaiting, в то время как при ошибке один извлекает этот класс и добавляет «lazyImageError» для его замены. Тайм-аут в обработчике ошибок нужен для того, чтобы дать время для применения нашего класса «произошла ошибка», чтобы мы могли применить второй класс для запуска анимации. Если загрузить все сразу, анимации ошибки не будет.

По сути, все, что нужно сделать JavaScript, - это поменять классы, когда это необходимо.

CSS: тяжелая атлетика.

Начнем с внешнего контейнера:

.lazyImage {
  position:relative;
  display:inline-block;
  width:100%;
  height:0;
  overflow:hidden;
  vertical-align:bottom;
}

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

100% ширина предназначена для того, чтобы родитель мог определить размер. Для этого может потребоваться дополнительный элемент в некоторых макетах для управления шириной, но без 100% ширины заполнение как высота имеет тенденцию быть совершенно шатким. Даже не ПЫТАЙТЕСЬ устанавливать max-width для .lazyImage, это мешает сохранению соотношения сторон.

Я сделал это для того, чтобы ширина была динамической, а не фиксированной, и потому, что пока изображение не загружено, CSS не знает, насколько оно велико. Вы также можете установить его как фиксированный размер либо в CSS, либо из style = ””, но я предпочитаю позволить родительскому элементу управлять моими изображениями в потоке… как вы увидите в демонстрации.

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

Изображение:

.lazyImage img {
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
  opacity:1;
  transform:scale(1);
  transition:background 0.5s, opacity 0.5s, transform 0.5s;
}

Абсолютно позиционируется внутри нашего контейнера, так что другие вещи, такие как анимация загрузки, фоны-заполнители и т. Д., Также могут быть расположены вокруг него. Кроме того, 100% высота / ширина контейнера сохранит соотношение сторон, которое мы указываем в разметке. Непрозрачность / масштаб / переход - это все «загруженное» состояние, когда оно ожидает загрузки:

.lazyImageError img,
.lazyImageWaiting img {
  opacity:0;
  transform:scale(0);
  transition:none;
}

Поскольку он скрыт во время загрузки. Некоторые из вас могут подумать: «Почему бы просто не добавить класс, когда он загружен». Причина, по которой я здесь, заключается в том, что мы все равно хотели бы, чтобы класс сообщал, что сценарий активен, поскольку постепенное отключение / блокирование сценариев деградации - это то, что мы также должны планировать. Зачем нужны четыре класса - один для «все время», один для «сценариев загружен и ожидает», один для «сценариев завершен» и еще один для обработки ошибок, когда мы могли бы просто использовать три?

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

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

.lazyImageError:after {
  content:"Image Not Found";
  box-sizing:border-box;
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
  overflow:auto;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:1em;
  background:#FF4;
  color:#F00;
  border:0.5em dashed #F00;
  transform:scale(0);
  transition:transform 0.5s;
}
.lazyImageErrorShow:after {
  transform:scale(1);
}

Обработка ошибок из-за сбоя загрузки изображения - это то, о чем Я видел МНОГО существующих реализаций, о которых не думают.

Живая демонстрация шага 1

Я добавил его в свой стандартный демонстрационный шаблон, чтобы вы могли увидеть его в действии:



Необычная демонстрация« ленивой загрузки изображений
Приведенные ниже изображения в современных браузерах загружаются с использованием атрибута HTML 5 'new'-ish loading =' lazy '. Сценарий был… cutcodedown.com »



Шаг 2: загрузка анимации

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

.lazyImage:before {
  content:"";
  position:absolute;
  width:30%;
  height:0;
  padding-top:30%;
  top:50%;
  left:50%;
  opacity:0;
  transform:translate(-50%,-50%);
  transition:transform 0.5s, opacity 0.5s;
  border:1em solid;
  border-color:#0484 #0488;
  border-radius:50%;
}
.lazyImageWaiting:before {
  animation:spin 1s linear infinite;
  opacity:1;
}
@keyframes spin {
  0% {
    transform:translate(-50%,-50%) rotate(0deg) scale(1);
  }
  50% {
    transform:translate(-50%,-50%) rotate(180deg) scale(0.7);
  }
  100% {
    transform:translate(-50%,-50%) rotate(360deg) scale(1);
  }
}

Типичные верхние / левые 50% переводят -50% для центрирования, анимация постепенного исчезновения после завершения загрузки, ничего необычного.

Живая демонстрация

В демонстрации для этого я добавил дополнительный раздел, чтобы постоянно показывать анимацию загрузки.
https://cutcodedown.com/for_others/medium_articles/fancyLazyImages/fancyLazyImages_step2_loadingAnimation.html

Шаг 3. Фон-заполнитель

Не всем нравится загрузка анимации, и вместо этого может потребоваться фоновое изображение в качестве заполнителя. Фактически, вдохновение для этой статьи пришло из вопроса на CodingForum.net, где пользователь Akbooko хотел сделать именно это.

Проблема в том, что если мы просто попытаемся загрузить изображение или использовать фоновое изображение, это изображение тоже должно загрузиться, а это значит, что оно тоже не отображается сразу. Как некоторые из вас, возможно, догадались, ответ на эту проблему состоит в том, чтобы использовать кодировку base64 желаемого изображения непосредственно в CSS, таким образом, он загружается одновременно с макетом, поэтому в начале не будет «вспышки» без изображения. .

Поэтому вместо кода загрузки анимации мы просто:

.lazyImage:before {
 content:"";
 position:absolute;
 top:0;
 left:0;
 width:100%;
 height:100%;
 background:url(
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAulBMVEUBAAD/AAAIAAD/CgoOAAAZAAAUAAD/BQUeAAAuAAA9AAA4AAAoAAAjAADmAABCAAA0AAD/IiLhAAD/HR3ZAACcAAD/GBj6AADSAAB8AABMAAD/MDD/EhKxAACYAABRAAD/KSm5AACTAAB2AABHAAD1AADGAACBAADxAACkAACJAADqAACrAABqAABmAADJAADBAACgAACFAABhAACNAAC+AABdAABVAADeAADuAACPAABwAADNAAD/OTmppkF+AAAEnklEQVQ4yxSSWbKiQBREb3rVaocuLYrRAkQQZFIBh6c+3f+22v4/kZGRechNbJQFlCxMAz/UCgmcxc2PplGfUus/17GM6A1ixYbxNh4nyN0QbJKk4vGiW23XfUl3JaY3a9ZXpLv8C3o5bChlBAD2crP7gRevljwc5uF8PkOwpBwGENoqFbzE9wXsHAfndR0v7Ivj8IGGArMsdQjQBRpICJw/D0hPQDMdCntU7FoK4mB6Kfbjm0MKsJIzyqeCAD4MYyMclnF7GeVytXH4OlsVp1NFpuNQPjnJAeWVLBl2aHmH/T5ajTa7YVqb6wjRMKazr6VWLPEFlGA0XmlPAj8YZWKgtP3Zds2J6tAmGGbz0AkERIKQ4XVWvBnO0aSUVeZYv3Q439M5k4IE+N2EGq5i92kJuF22bVHPf1s/iKLXMVCX8Z3gGm1KIfOQhftN+3RAn8pqWw2LALtDulvM7hwQicIufcl4iI/+svwwrMax9ibZrtzc7Drz385o3d8o6TQAu9M+5wY6tAyzM+mbFcVDRQGL+WZop45FFnOBhxuqEMyWZ5Xyq8mJdvpwOe7qbfq4/Vm3Ed1IMIuELSMA8WQoDe0u85jix2tq8y8dq/h0FacRcXNOGI1A8u0HlUt8MGvDLA2e6ThtuI34ulxFR2IhE9dK4AowXKiQDV++Mx+nM3tNO29Wbbmjy4NcV8NjwPw/uysB9jT29CrmVJ8D6hGsaxnse7JYg23gLUOPn57yuFHBs15ucFz99tmktn+WDn4mpBIIZpYAc6k/sLSLReYG9z33yxj239p+kVPu/7VAbstpA0EQbcazy17ZNbGwUFlSdAGZi0QAAQ74/78ruCozD/3S1ae60WWP2bfdv3Z5dah8Nhb6vjLwUrA0FkAEICIIG+N3SYH1e8rnlwZfv26uEMozNMkAAUQjYgZCmtz7MDm779SKzecEf+Z7a6Ad2HtuFQzARkZG9lisOF0V4u3bJZd5GPvF8EQDUEYwe7awkF7gFm7vOdl89Nm7Dvnp2NsNNMhLTSyUAbyQrSK89uYz7Xi9+zMM60I1Se1L0k4qBCtF6ZWDFnDAZr/vj3X5SNdreZ1n1PxdNrE0IBUzR8/DzdmoCPpldkZXDQiHZFkv0qap6owUZwrOEgKJoDRZgdE3023sdje1qe7YFwMvLxw9iEoQ8FPcsmwFft9MUdX2a1d3b2NANt25B6CZACfV06MiGSEJ6T3gre6w/FWKyUeHR7KFVWQIlkxQpIRUioigq4WVp7+nWzmJy6ZKaD6pQoRgAy0oMHEU0nmgl4fLJSabkI3znK650fnIgrQiKp10gpSFgDb4sLS6X4/Dep5tpydznZ3p3Skd9Q+dgPD8H0Uzv+abw+F8mlt8TmvXX7ajapm11ELBe20ZpAIwJHmESY+yfPGqeSlj330Tac9MQGuRUZSsgoQXyeEo0sXZZdUXZ/MW5ZYGeND/kRhCkG8xOdvL683luzsdZrV/24xZ6lmXTy4JVuRaGDA59OtlO6z25fWjeGwXZyoO/UAeAEmwN2z0c56oJGQx7a6zWaRkNru2K0/baWjxzIJjQEoARND8D7+tgCt2HKJNAAAAAElFTkSuQmCC'
 ) center center repeat;
 opacity:0;
 transition:opacity 0.5s;
}
.lazyImageWaiting:before {
 opacity:1;
 transition:opacity 0s;
}

Как видите, это не сильно отличается от нашей анимированной версии, вместо этого мы просто загружаем фоновое изображение. Я также убрал этот заполнитель, так как возможно, вы используете что-то вроде alpha transparent .png, где вы не хотите, чтобы заполнитель отображался после завершения загрузки.

Живая демонстрация

И вот, это работает. Как и в случае с анимацией, у меня есть дополнительный раздел, показывающий фоновое изображение.
https://cutcodedown.com/for_others/medium_articles/fancyLazyImages/fancyLazyImages_step3_placeholder_image.html

Демо-репозиторий

Как и во всех моих примерах, каталог:
https://cutcodedown.com/for_others/medium_articles/fancyLazyImages

… Широко открыт для легкого доступа к липким фрагментам, а также ко всем другим демонстрационным материалам. Там также есть .rar всего shebang.

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

Продолжая

С минимальными усилиями это можно было бы расширить, чтобы изображения, которые кэшируются / уже загружены, имели дополнительный крючок прокрутки, чтобы анимация срабатывала в любом случае, чего в настоящее время не происходит. Прямо сейчас кешированные изображения уже отображаются. Класс «ожидания» можно просто добавить ко всем элементам, и, если он не основан на «загрузке», вместо этого примените прокрутку. Если сценарий проверяет наличие! Img.complete, если он уже завершен, просто отметьте его для проверки прокрутки.

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

Проблемы и опасения

Есть некоторые вещи, о которых нужно знать; некоторые проблемы, некоторые нет.

Браузер / загрузка системы

В то время как в моем собственном тестировании всего, от Ryzen 5 3600 + GTX 1070 в моем медиацентре до Celeron J1900 и GT 740 на моей рабочей станции, все работало гладко, у меня было несколько сообщений о том, что анимация была прерывистой / дергающейся. . Я подозреваю, что это либо проблема комбинации ОС / браузера, либо меньшие видеокарты, такие как интегрированная графика Intel, просто не успевают за выбранной мной анимацией.

Время от времени это может быть проблемой с анимацией CSS3, и для этого вам иногда нужно не забывать «держать это в штанах», когда дело касается этих эффектов. Я подозреваю, что если вы сведете количество анимаций / элементов к минимуму, это нормально ... Но постарайтесь помнить, что чем больше их вы помещаете на страницу, тем большую нагрузку на CPU / GPU вы вызываете у пользователя. Вы помещаете их 30+, чтобы они умещались на экране одновременно, не удивляйтесь, если система выйдет из строя.

Internet Explorer

Еще одна проблема заключается в том, что Interdebt Exploder просто не может обрабатывать многие современные макеты и эффекты. Мое решение - как вы можете видеть в демонстрационной разметке - установить X-UA-Compatible для IE9, восстановив функциональность условных комментариев, заключив все стили и сценарии в комментарии! IE, чтобы они не отправлялись в IE. Аналогичным образом для IE может отображаться сообщение с предупреждением об обновленном браузере.

Если у вас есть правильная семантическая разметка и изящная деградация, это даст устаревшему IE полезную, хотя и скучную и уродливую страницу. Теперь это действительно должно быть окончательной поддержкой IE на данный момент. Мы не можем продолжать отступать, пытаясь сделать так, чтобы браузер, устаревший более чем на десять лет, продолжал показывать все наши модные макеты и эффекты. Это непродуктивно, нежизнеспособно и совершенно нереально ожидать. По крайней мере, если клиент не готов доплачивать за это!

Я не говорю, что их можно полностью оставить в стороне, но если у вас есть правильная семантическая разметка, которая изящно ухудшается для невизуальных UA - программ чтения с экрана, устройств чтения Брайля, поисковых систем - кормить только этим одна и та же ванильная разметка для всех пользователей IE дает им то, что должно РАБОТАТЬ. Это просто некрасиво; О нет, ура !!!

Сказать IE, чтобы он не пошел этим путем, может быть хорошей темой для другой статьи…

Доступность

Способ кодирования - через «прогрессивное улучшение» - страница будет постепенно ухудшаться, если скрипты или стиль будут заблокированы / недоступны / неактуальны. Таким образом, он не должен - и при ограниченном тестировании - не оказывать никакого влияния на доступность, поиск и другие подобные проблемы.

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

Но даже в этом случае очень немногие разработчики - особенно фанаты JavaScript и интерфейсных фреймворков - обращают внимание на доступность. Это действительно должно вызывать беспокойство, учитывая, что сезон открыт для ВСЕХ предприятий с тех пор, как Верховный суд США отказался рассматривать апелляцию Domino, поддержав решение суда низшей инстанции против них.

Заключение

Многие новые функции HTML 5 и CSS3 избавляют JavaScript от «рутинной работы». При использовании «прогрессивного улучшения» эти методы могут устранить любые проблемы с доступностью, сделать «потребность» в фреймворках JavaScript скорее мифом, чем фактом. Хотя есть недостатки с точки зрения поддержки устаревших браузеров, это становится все менее и менее серьезной проблемой.