Blog. Just Blog

Загрузка видимых изображений (Lazy Load) на JavaScript

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Web-мастеру и не только | Автор: ManHunter
Загрузка видимых изображений (Lazy Load) на JavaScript
Загрузка видимых изображений (Lazy Load) на JavaScript

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

Готовых скриптов Lazy Load для разных популярных JavaScript-фреймворков написано уже немало. Мне же, как всегда, захотелось реализовать свой вариант. Да и вам, наверное, будет интересно узнать как это все работает.

Сперва сделаем заготовку HTML-страницы. Это будет контейнер галереи, в котором будут размещены несколько картинок. Ничего сложного, самая обычная верстка:
  1. <div id="gallery">
  2.   <img src="/images/image01.jpg" width="600" height="450" alt="Фото 1">
  3.   <img src="/images/image02.jpg" width="600" height="450" alt="Фото 2">
  4.   <img src="/images/image03.jpg" width="600" height="450" alt="Фото 3">
  5.   <img src="/images/image04.jpg" width="600" height="450" alt="Фото 4">
  6.   <img src="/images/image05.jpg" width="600" height="450" alt="Фото 5">
  7.   <img src="/images/image06.jpg" width="600" height="450" alt="Фото 6">
  8.   <img src="/images/image07.jpg" width="600" height="450" alt="Фото 7">
  9.   <img src="/images/image08.jpg" width="600" height="450" alt="Фото 8">
  10.   <img src="/images/image09.jpg" width="600" height="450" alt="Фото 9">
  11.   <img src="/images/image10.jpg" width="600" height="450" alt="Фото 10">
  12.   <img src="/images/image11.jpg" width="600" height="450" alt="Фото 11">
  13.   <img src="/images/image12.jpg" width="600" height="450" alt="Фото 12">
  14. </div>
Теперь перейдем к теоретической выкладке. Как работает Lazy Load? После загрузки HTML-содержимого страницы, но до момента ее полной загрузки (все скрипты и изображения), запускается скрипт, который заменяет все изображения в отслеживаемой области на "заглушку" - однопиксельный прозрачный GIF-файл. Чтобы верстка страницы при этом не поплыла, размеры всех оригинальных картинок обязательно должны быть указаны. Данные о ссылках на исходные картинки удобнее всего сохранять прямо в элементе img, добавляя ему наш собственный атрибут lazy. После этого устанавливается обработчик событий на прокрутку окна и изменение размеров. На каждое срабатывание события (пользователь прокручивает страницу), скрипт пробегает по всем картинкам в контейнере, высчитывает их положение относительно верхнего края страницы, и, если они попадают в видимую область, то заменяет атрибут src на сохраненный атрибут lazy. В этом случае картинка будет загружаться с сервера или будет взята из кеша браузера, если он была ранее туда загружена. Для ускорения проверки у отображаемых картинок атрибут lazy можно удалять. Осталось оформить это все в виде скрипта. Нам понадобится вспомогательная функция, которая будет возвращать координаты местоположения элемента на странице относительно верха страницы.
  1. // Вспомогательная функция для определения координат элемента
  2. function lazy_get_position(element) {
  3.     var offsetLeft=0;
  4.     var offsetTop=0;
  5.     do {
  6.         offsetLeft+=element.offsetLeft;
  7.         offsetTop+=element.offsetTop;
  8.     }
  9.     while (element=element.offsetParent);
  10.     return {x:offsetLefty:offsetTop};
  11. }
Теперь напишем функцию инициализации. Для повышения быстродействия мы будем обрабатывать не все картинки на странице, а только те, которые находятся в нужном нам контейнере-галерее. Он задается через ID родительского элемента, в нашем случае это "gallery":
  1. // ID родительского элемента
  2. var lazy_parent_id='gallery';
  3.  
  4. function lazy_load() {
  5.  
  6.     // Картинка-заместитель
  7.     var lazy_replacer='dot.gif';
  8.     var container=document.getElementById(lazy_parent_id);
  9.     if (container) {
  10.         var im=container.getElementsByTagName('img');
  11.         // Сохранить адрес исходной картинки и заменить его на прозрачный GIF
  12.         for (var i=0i<im.lengthi++) {
  13.             var el=im[i];
  14.             if (el.src) {
  15.                 el.setAttribute('lazy',el.src);
  16.                 el.lazy=el.src;
  17.                 el.src=lazy_replacer;
  18.             }    
  19.         }    
  20.     }
  21.  
  22.     // Установить обработчики событий окна
  23.     window.onscroll lazy_load_proc;
  24.     window.onresize lazy_load_proc;
  25.     // Сразу же показать картинки в видимой области
  26.     lazy_load_proc();
  27. }
Осталось написать обработчик, который будет показывать картинки в видимой области. Я его подробно прокомментировал, непонятностей быть не должно.
  1. // Обработчик событий прокрутки
  2. function lazy_load_proc() {
  3.     var doc document.documentElement;
  4.     var body document.body;
  5.  
  6.     // Получить размеры видимой области страницы (кроссбраузерно)
  7.     if (typeof(window.innerWidth) == 'number') {
  8.         my_width window.innerWidth;
  9.         my_height window.innerHeight;
  10.     }
  11.     else if (doc && (doc.clientWidth || doc.clientHeight)) {
  12.         my_width doc.clientWidth;
  13.         my_height doc.clientHeight;
  14.     }
  15.     else if (body && (body.clientWidth || body.clientHeight)) {
  16.         my_width body.clientWidth;
  17.         my_height body.clientHeight;
  18.     }
  19.  
  20.     // Получить смещение страницы относительно ее верха
  21.     if (doc.scrollTop) { dy=doc.scrollTop; } else { dy=body.scrollTop; }
  22.  
  23.     // Обработка всех картинок в контейнере
  24.     var container=document.getElementById(lazy_parent_id);
  25.     if (container) {
  26.         var im=container.getElementsByTagName('img');
  27.         for (var i=0i<im.lengthi++) {
  28.             var el=im[i];
  29.             // Если атрибут lazy есть, то обработать картинку
  30.             if (el.lazy) {
  31.                 // Получить координаты картинки от верха страницы
  32.                 var coord=lazy_get_position(el);
  33.                 // Если картинка попала в видимую область, то показать ее.
  34.                 // Плюс берется запас в 100 пикселов для более плавной подгрузки
  35.                 if (coord.y>(dy-my_height-100) && coord.y<(dy+my_height+100)) {
  36.                     // Прописать адрес исходной картинки и убрать атрибут lazy
  37.                     el.src=el.lazy;
  38.                     el.setAttribute('src',el.lazy);
  39.                     el.lazy='';
  40.                     el.removeAttribute('lazy');                
  41.                 }    
  42.             }
  43.         }
  44.     }
  45. }
Последний шаг. Осталось все это поместить на нашу страницу. Текст функций для краткости я дублировать не стал, они все описаны выше. Должно получиться примерно следующее:
  1. <script type="text/javascript">
  2. // ID родительского элемента
  3. var lazy_parent_id='gallery';
  4. function lazy_load() { ... }
  5. function lazy_load_proc() { ... }
  6. function lazy_get_position(element) { ... }
  7. </script>
  8.  
  9. <div id="gallery">
  10.   <img src="/images/image01.jpg" width="600" height="450" alt="Фото 1">
  11.   <img src="/images/image02.jpg" width="600" height="450" alt="Фото 2">
  12.   <img src="/images/image03.jpg" width="600" height="450" alt="Фото 3">
  13.   <img src="/images/image04.jpg" width="600" height="450" alt="Фото 4">
  14.   <img src="/images/image05.jpg" width="600" height="450" alt="Фото 5">
  15.   <img src="/images/image06.jpg" width="600" height="450" alt="Фото 6">
  16.   <img src="/images/image07.jpg" width="600" height="450" alt="Фото 7">
  17.   <img src="/images/image08.jpg" width="600" height="450" alt="Фото 8">
  18.   <img src="/images/image09.jpg" width="600" height="450" alt="Фото 9">
  19.   <img src="/images/image10.jpg" width="600" height="450" alt="Фото 10">
  20.   <img src="/images/image11.jpg" width="600" height="450" alt="Фото 11">
  21.   <img src="/images/image12.jpg" width="600" height="450" alt="Фото 12">
  22. </div>
  23.  
  24. <script type="text/javascript">
  25. // Инициализировать скрипт
  26. lazy_load();
  27. </script>
Если используются какие-то посторонние фреймворки, то запуск lazy_load() надо забиндить на событие "ondocumentready", то есть когда уже произошла полная загрузка HTML и скриптов, но еще не загружены все картинки. Вешать запуск Lazy Load на событие "onload" бессмысленно, так как при его наступлении все картинки уже загружены, и никакой пользы от использования этой технологии не будет. На индексацию поисковыми системами она также не влияет, ведь в HTML-коде, который "видит" робот-поисковик, прописаны ссылки на оригинальные картинки. Также этот метод будет работать при отключенных скриптах, пользователь просто будет вынужден загрузить сразу все картинки.

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

Изменение содержимого страницы в FireBug
Изменение содержимого страницы в FireBug

UPD. После обсуждения скрипта был выявлен его существенный недостаток. Стационарные браузеры все равно отправляют запросы на сервер и загружают все картинки, даже если они были скрыты и/или ссылки на них была заменены скриптом. Браузеры на мобильных устройствах ведут себя более экономно к сетевым ресурсам, и скрипт Lazy Load там отрабатывает как задумано. Есть несколько способов решения.

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

Второй способ. После формирования DOM-дерева (загружены скрипты и HTML страницы) и после запуска скрипта Lazy Load можно принудительно останавливать дальнейшую загрузку страницы. Делается это примерно так:
  1. try {
  2.     // Остановить загрузку страницы для Firefox, Chrome...
  3.     window.stop();
  4. }
  5. catch(e) {
  6.     try {
  7.         // Остановить загрузку страницы для Internet Explorer
  8.         document.execCommand('stop');
  9.     }
  10.     catch(e) { 
  11.         // Фокус не удался, функция в браузере не поддерживается
  12.     }
  13. }
При выполнении этого кода загрузка страницы будет остановлена, как будто была нажата кнопка "Стоп" в браузере. Загрузка даже запрошенных картинок будет незамедлительно прекращена. Плюсы в том, что не надо менять текст страницы, то есть индексироваться сайт будет правильно, а пользователи с отключенными скриптами не будут обижены. Минусы этого способа, что далеко не все браузеры поддерживают такую остановку загрузки страницы. А главное, что под отмену загрузки может попасть и другой контент, например, flash-ролики или элементы аудио-видео, или страница вообще может повести себя еще как-нибудь непредсказуемо. Реальных примеров с использованием такого способа я не встречал, так что он остается в разряде "proof of concept".

UPD2. Современные браузеры поддерживают для изображений атрибут loading="lazy", но его поддержка более старыми браузерами оставляет желать лучшего. Зато использование этого атрибута решает задачу отложенной загрузки изображений наиболее эффективно.

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

Поделиться ссылкой ВКонтакте
Просмотров: 16727 | Комментариев: 12

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Петр (09.06.2016 в 09:51):
Ну а как быть с мобильной версией сайта?
Вы тут указываете явные размеры картинок.
Олег (09.04.2016 в 12:14):
Здравствуйте! Подскажите, пожалуйста, как заставить подгружаться не только изображения, но и iframe?
Такая строчка в скрипте не помогла
var im=container.getElementsByTagName('iframe');
Max (31.01.2014 в 21:41):
Да замечу почему на планшете грузятся "по другому".
У разных браузеров разный механизм обработки запросов.
В Firefox - запросы к серверу идут в любом случае если стоит путь.
ManHunter (29.01.2014 в 14:20):
Дополнил статью возможными решениями. Спасибо!
Max (29.01.2014 в 14:18):
Вот видите, я тоже думал ваш скрипт "панацея" уже обрадовался, но по логике понял что не получиться, проверил - точно, запросы идут.
Тогда лучше использовать готовый скрипт http://www.appelsiini.net/projects/lazyload

Так что я тоже огорчился, но так и предполагал, думаю в http://www.appelsiini.net/projects/lazyload  не дураки делали, тоже хотели идеально сделать чтобы и поисковики видели и т.п. Но... не получиться по тех. причинам
;(
ManHunter (29.01.2014 в 13:34):
Действительно на стационарном компе грузится все, хоть и в фоне. На планшете с мобильного интернета картинки грузятся только при прокрутке.
Единственное решение я уже указывал - на этапе формирования страницы проставлять всем картинкам src=dot.gif, а при отображении подменять на правильный src. При этом обломятся поисковики картинок и пользователи с отключенными скриптами.

Как еще один вариант можно принудительно остановить загрузку страницы по готовности DOM через window.stop() для браузеров и document.execCommand('stop') для некоторых IE. Программный аналог кнопки Стоп в браузере.
Max (29.01.2014 в 05:45):
Не будет он выполнять свое прямое предназначение.
Если в src стоит путь после формирования DOM - будет сформирован и отправлен запрос.
Т.е. запросов к серверу будет в два раза больше.
Михаил (02.11.2012 в 12:24):
да, действительно, только в моем случае скрипт будет постоянно подгружать превьюшки, а листалка будет брать полновесные картинки, спасибо за разьяснения, и за скрипт
ManHunter (02.11.2012 в 11:48):
Берешь и допиливаешь листалку, чтобы она проверяла наличие атрибута lazy и работала аналогично lazyload. И вообще, для галерей всегда делается маленькая легкая превьюшка, которая является _ссылкой_ на полный вариант картинки. Именно эти ссылки и должна обрабатывать листалка, и никак иначе. А этот скрипт заточен на полновесные картинки.
Михаил (02.11.2012 в 11:42):
Здравствуйте, а что если изображение увеличивается при нажатии, и в увеличеном виде есть пролистывание изображений, берутся все картинки галереи, то пролистаются только изображения которые находятся в видимой области, а дальше будет листаться однопиксельный прозрачный GIF-файл?
Александр (14.08.2012 в 14:52):
Всегда интересно узнать что-то новое. Спасибо.
Игорь (12.08.2012 в 05:25):
Благодарю за вашу статью, применил вашу разработку на своем ресурсе:)

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

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2025
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0047 сек.) / Память: 4.5 Mb
Наверх