Ускоряем сайт. Отложенная загрузка изображений. Mootools, jQuery
Хороший сайт должен грузиться быстро. Этот момент стоит учитывать и при создании сайтов, и при анализе и оптимизации уже существующих сайтов. При загрузке страниц интернет-проекта посетители не любят слишком долго ждать.
В этой статье мы поговорим об «ускорении» загрузки сайта при помощи «отложенной загрузки изображений» на Javascript, приведем примеры реализации для известных framework ов mootools и jQuery. Это особенно актуально для сайтов, которые используют изображения высокого качества (новостные порталы, сайты, посвященные фотографии, дизайну, клипарту, социальные сети, объемные блоги и т.п.)
Отложенная загрузка изображений при помощи javascript с использованием mootools, jQuery
Какие неприятности нас ожидают во время загрузки изображений? При обычной загрузке изображения, если встречается битое или отсутствующее изображение, или картинка загружается с другого источника, или картинка «слишком медленно» грузится — сайт «подвисает».
Его загрузка не продолжится до тех пор, пока мы не получим текущее изображение, или не истечет минутный timeout. Что же видит посетитель? Зачастую – ничего, просто белый экран, или обрывочную часть сайта. Будет ли он терпеть такую «выходку» сайта и вернется ли когда-нибудь снова?
При помощи отложенной загрузки изображений мы можем этого избежать. И при этом изображения при способе «отложенной загрузки» грузятся асинхронно, в произвольном порядке, после полной загрузки основного содержимого сайта, давая возможность посетителю приступить к чтению и просмотру интересующей его информации. Визуально глазами посетителя это существенно «ускоряет» загрузку страницы.
Идея отложенной загрузки изображений на javascript
Идея, в общем-то, простая. Есть такое событие в javascript «domready». Которое наступает во время получения полной структуры html документа и еще до полной загрузки страницы. Так вот, после наступления этого события мы получаем список нужных изображений, «сбрасываем» атрибут src каждой картинки, устанавливая для него значение, например, анкор: #. Src же изображения сохраняем в отдельном атрибуте изображения, например, longDesc. Скрываем картинку, назначая css правило: visibility: hidden.
После полной загрузки страницы по событию window.onload каждой картинке «возвращаем» её родной атрибут src (что, по сути, вызывает «загрузку» оригинального изображения). Картинки одна за другой начинают «появляться» на странице.
Усовершенствуем идею отложенной загрузки
А что если загружать отложено только те изображения, которые видит посетитель. В видимой части экрана? И по мере надобности, когда посетитель воспользуется кнопкой scroll а, догружать остальные?
Что же еще? Было бы хорошо визуально для посетителя показать, что на странице что-то происходит. Например, что отложенные изображения грузятся. Для этого достаточно вставить некий элемент span сразу после изображения, назначить ему css класс preloader и в рамках css стилизовать элемент.
После чего назначить background ом тематическое интуитивно понятное динамическое изображение загрузки, скажем, в формате gif. Теперь после полной загрузки текущего изображения по наступлению события объекта image onload удалить элемент span.preloader и отобразить само изображение, присвоив картинке css правило: visibility: visible;
Реализация отложенной загрузки изображений на Javascript
А вот здесь всплывает проблема номер один. Это удивительно! Но до сих пор ни один браузер не поддерживает нормально у объекта image событие onload! Событие наступает стихийно.
Изображения в итоге то появляются, то не появляются вовсе, причем даже то же самое изображение во время разных загрузок одной и той же страницы: событие может вовсе не происходить, или происходит раньше, до полной загрузки картинки.
Эмуляция события onload для объекта Image
Итак, подумав, мы пришли к выводу, что изображение считается загруженным только тогда, когда у него атрибуты длинна и высота: width, height становятся отличными от 0. Но здесь, как обычно, обрадовал Internet Explorer, который при неудачной попытке загрузить изображение «подставляет» вместо него свое «по умолчанию» с красным крестиком, у которого тоже есть длинна и высота.
Исследовав дальше свойства объекта image, мы обнаружили, что нужно также проверять свойство complete – которое показывает, было ли изображение загружено полностью. Кроме этого обрадовали Safari, Google Chrome, построенные на движке Webkit, по их мнению было ли изображение загружено или нет – без разницы, complete всегда true.
Так что приходится обрабатывать для каждого изображения события onerror, onabort, и в случае их возникновения (при ошибке загрузки), присваивать атрибутам width, height изображения 0 значение. Итак, в итоге мы проверяем эти три свойства объекта image через определенный интервал времени.
Как только complete = true, а width, height отличны от 0 – изображение загружено полностью и не является битым. Но, чтобы не «плодить» бесконечные интервальные события, мы определим максимальное число времени, через которое отменим интервальное событие проверки для текущего изображения, будем считать, что изображение получить невозможно, и скроем объект его загрузки.
А также разобьем все изображения на две группы, те, которые находятся в видимой области экрана посетителя, их же мы добавим к загрузке сразу после полной загрузки страницы, и те, которые находятся в невидимой области экрана, эти изображения мы загрузим при наступлении события onscroll – когда посетитель воспользуется scroll ом мышки.
Реализация отложенной загрузки изображений на Mootools и jQuery
Позвольте Вам привести два исполнения отложенной загрузки изображений на Mootools и jQuery.
Mootools 1.1 – 1.4.4:
<script type="text/javascript"> /** delayed image load by Black#FFFFFF **/ loadWait = 30000; loadCheck = 300; preloadObjects = "img"; notImagesLoaded = []; excludeImages = false; //excludeImages = "exclude" function getScreenHeight(){ var myHeight = 0; if( typeof( window.innerHeight ) == "number" ) { //Non-IE myHeight = window.innerHeight; } else if( document.documentElement && ( document.documentElement.clientHeight || document.documentElement.clientHeight )){ //IE 6+ in "standards compliant mode" myHeight = document.documentElement.clientHeight; } else if( document.body && ( document.body.clientHeight || document.body.clientHeight ) ) { //IE 4 compatible myHeight = document.body.clientHeight; } return myHeight; } function preloadOther(){ var l = notImagesLoaded.length; var currentExists = false; for(var i = 0; i < l; i ++){ var item = notImagesLoaded[i]; if(item){ loadImage(item); currentExists = true; }; }; if(!currentExists){ notImagesLoaded = []; window.removeEvent("scroll",preloadOther); }; }; function imagesPreloader(){ $$(preloadObjects).each( function(item){ if(item.nodeName.toLowerCase() == "img" && ( typeof excludeImages == "undefined" || excludeImages == false || (item.className.indexOf(excludeImages) == -1) ) ){ item.longDesc = item.src; item.src = "#"; var preloaderElt= new Element("span",{ styles:{"display":"inline-block"}}); $(preloaderElt).className = "preloader "+item.className; preloaderElt.inject(item,"before"); loadImage(item); }; } ); window.addEvent("scroll",preloadOther); }; function loadImage(item){ var pos = $(item).getPosition(); var ItemOffsetTop = typeof pos == "object" && typeof pos.y != "undefined" ? pos.y : 0; var documentScrollTop = $(window).getScrollTop(); var scrHeight= getScreenHeight(); if(ItemOffsetTop <= (documentScrollTop + scrHeight) && typeof item.storePeriod == "undefined"){ $(item).src = $(item).longDesc; item.onerror = function(){ this.width = 0; this.height = 0; } item.onabort = function(){ this.width = 0; this.height = 0; } item.wait= 0; item.storePeriod = function(){ this.wait += loadCheck; if(this.width && this.height && this.complete){ $clear(this.storePeriod); this.storePeriod = false; if(typeof $(this.previousSibling).destroy == "function"){ $(this.previousSibling).destroy(); } else { $(this.previousSibling).remove(); } $(this).setStyle("visibility","visible"); if(typeof this.loadedCount != "undefined" && notImagesLoaded[this.loadedCount]){ notImagesLoaded[this.loadedCount] = false; }; } else if(this.wait > loadWait){ $clear(this.storePeriod); this.storePeriod = false; if(typeof this.loadedCount != "undefined" && notImagesLoaded[this.loadedCount]){ notImagesLoaded[this.loadedCount] = false; }; $(this).setStyles({"display":"none","visibility":"hidden"}); if(typeof $(this.previousSibling).destroy == "function"){ $(this.previousSibling).destroy(); } else { $(this.previousSibling).remove(); } } }.periodical(loadCheck,item); } else { if(typeof item.loadedCount == "undefined"){ item.loadedCount = notImagesLoaded.length; notImagesLoaded[item.loadedCount] = item; }; }; }; $(window).addEvent("domready",imagesPreloader); </script>
jQuery 1.3.2 – 1.7.1:
<script type="text/javascript"> /** delayed image load by Black#FFFFFF **/ loadWait = 30000; loadCheck = 300; preloadObjects = "img"; notImagesLoaded = []; excludeImages = false; function getScreenHeight(){ var myHeight = 0; if( typeof( window.innerHeight ) == "number" ) { //Non-IE myHeight = window.innerHeight; } else if( document.documentElement && ( document.documentElement.clientHeight || document.documentElement.clientHeight )){ //IE 6+ in "standards compliant mode" myHeight = document.documentElement.clientHeight; } else if( document.body && ( document.body.clientHeight || document.body.clientHeight ) ) { //IE 4 compatible myHeight = document.body.clientHeight; } return myHeight; } function preloadOther(){ var l = notImagesLoaded.length; var currentExists = false; for(var i = 0; i < l; i ++){ var item = notImagesLoaded[i]; if(item){ loadImage(item); currentExists = true; }; }; if(!currentExists){ notImagesLoaded = []; jQuery(window).unbind("scroll",preloadOther); }; }; function imagesPreloader(){ jQuery(preloadObjects).each(function(){ var item = this; if(item.nodeName.toLowerCase() == "img" && ( typeof excludeImages == "undefined" || excludeImages == false || (item.className.indexOf(excludeImages) == -1) ) ){ item.longDesc = item.src; item.src = "#"; item.alt = ""; var preloaderElt= jQuery("<span></span>"); jQuery(preloaderElt).css({"display":"block"}); preloaderElt.className = "preloader "+item.className; jQuery(item).before(preloaderElt); loadImage(item); }; }); jQuery(window).bind("scroll",preloadOther); }; function loadImage(item){ var pos = jQuery(item).position(); var ItemOffsetTop = typeof pos == "object" && typeof pos.top != "undefined" ? pos.top : 0; var documentScrollTop = jQuery(window).scrollTop(); var scrHeight= getScreenHeight(); if(ItemOffsetTop <= (documentScrollTop + scrHeight) && typeof item.storePeriod == "undefined"){ item.src = item.longDesc; item.onerror = function(){ this.width = 0; this.height = 0; } item.onabort = function(){ this.width = 0; this.height = 0; } item.wait= 0; item.storePeriod = setInterval(function(){ item.wait += loadCheck; if(item.width && item.height && item.complete){ clearInterval(item.storePeriod); item.storePeriod = false; jQuery(item.previousSibling).remove(); jQuery(item).css("visibility","visible"); if(typeof item.loadedCount != "undefined" && notImagesLoaded[item.loadedCount]){ notImagesLoaded[item.loadedCount] = false; }; } else if(item.wait > loadWait){ clearInterval(item.storePeriod); item.storePeriod = false; if(typeof item.loadedCount != "undefined" && notImagesLoaded[item.loadedCount]){ notImagesLoaded[item.loadedCount] = false; }; jQuery(item).css({"display":"none","visibility":"hidden"}); jQuery(item.previousSibling).remove(); }; },loadCheck); } else { if(typeof item.loadedCount == "undefined"){ item.loadedCount = notImagesLoaded.length; notImagesLoaded[item.loadedCount] = item; }; }; }; jQuery(document).ready(imagesPreloader); </script>
Примеры внедрения отложенной загрузки изображений на javascript
Примечание: для использования в режиме совместимости с jQuery.noconflict() все вхождения знака $ заменены на передачу параметров объекту jQuery
Настройки javascript:
- preloadObjects = «img»; — здесь указываем селектор, откуда выбирать изображения? Можно выбрать все изображения на странице «img», можно выбрать только изображения с определенным классом, например: «img.lazyload», или в определенном родительском контейнере «#parentcontainer img».Примечание автора: так как выбор селектора для назначения отложенной загрузки изображений остается за Вами, мы решили добавить маленькую проверочку и выбрать только те элементы с указанными селекторами, которые будут являться изображениями, чтобы избежать конфликтных ситуаций.
- loadWait = 30000; -максимальное время в миллисекундах, до которого будет проводиться проверка загрузки изображения (эмуляция события image.onload), и после которого изображение будет считаться битым или незагруженным
- loadCheck = 300; — задержка в миллисекундах между интервалами проверки состояния изображения (загружено или нет?). Равна по умолчанию пол секунды, можно поставить и меньшее значение. Но не советуем Вам выставлять значение меньше 200 миллисекунд, если изображений много, то слишком быстрые интервальные «опросы» приведут к «тормозам» обозревателя.
- notImagesLoaded = []; — массив будет использоваться для хранения наших изображений
- excludeImages = false; — для определенных изображений, которые требуется исключить из «отложенной» загрузки, можно назначить дополнительный класс, имя которого назначить параметру excludeImages, например: excludeImages=»exclude»; исключит изображения с классом exlude из процесса «отложенной» загрузки. Делается это для избегания конфликтов с другими типами javascript библиотек, работающими на Вашем сайте над изображениями (только в том случае прибегайте к этому способу, когда конфликт действительно возникает).
Настройки css:
Для стилизации элемента, который отображает загрузку изображения, используется класс
span.preloader { display: block; background: url(путь к изображению загрузки); }
Здесь Вы сможете стилизовать элемент, который будет отображаться «вместо» изображения до полной его загрузки, как Вам будет угодно. А также этому элементу span.preloader будет присвоен дополнительный класс, соответствующий классу изображения, что упростит формирование css стилей.
Пояснение: это значит, что если у изображения, добавленного в очередь отложенной загрузки, класс class=»bigImage», то у «заглушки» span.preloader, которая будет «визуализировать» процесс загрузки, соответственно класс class=»preloader bigImage», по сути, к заглушке будут применяться все те правила, которые будут назначены для изображения. В итоге при отложенной загрузке изображений вид страниц сайта остается неизменным.
В каких браузерах тестировалась отложенная загрузка?
IE 7,8,9, Firefox 3.6 – 10, Opera 11.61, Safari, Chrome последние
Как убедиться в том, что «отложенная загрузка» действительно работает? И загружает изображения точно после загрузки страницы?
Даже невооруженным глазом после установки нашего решения Вы заметите, что сайт начал «грузиться» быстрее, а указанные для отложенной загрузки изображения «грузятся» только после полной загрузки текущей страницы.
Но, чтобы окончательно убедиться, загрузите, например, браузер Opera, откройте один из примеров и обратите внимание на то, что после полной загрузки страницы и при скроллинге вниз у Вас в строке состояния браузера (адресной строке) во время прокрутки визуально отображается загрузка изображений (меняется счетчик запрашиваемых изображений).
Будет ли версия отложенной загрузки на чистом javascript без использования jQuery, Mootools?
Да, но она требует написания и тестирования дополнительных функций, так что появится немного позже.
Приветствую! Очень полезная статья!! У меня стоит задача ускорить загрузку сайта.. но знаний недостаточно в программировании. Буду пробывать, если будут трудности могу ли я рассчитывать на помощь?? Спасибо)