最近在做移動端的營銷頁面時,遇到了頁面有大量圖片的情況,於是很自然的想到了要使用圖片lazyload,PC端用着jQuery,也有現成的插件。
但是在移動端,基本不用jQuery,於是就試着自己去造一下輪子。
實現lazyload並不難,我很快就想到以下幾個步驟:
- 首先HTML中不直接寫圖片真實URL,而是用一個空圖代替,如<img class="lazy" src="images/nopic.png" data-original="images/test.jpg">
- 監聽滾動scroll事件
- 判斷圖片元素是否出現在屏幕中,如果是則替換src為data-original里的真實URL。
在這簡單的幾個步驟中,也有幾個細節要點需要注意:
- 監聽事件是scroll,需要考慮是否應用函數節流(畢竟移動端,性能問題不能忘)
- 在移動端,水平X方向的滾動是很方便的,判斷圖片是否出現時,需要考慮水平方向。
- 滾動一定距離再刷新頁面時,頁面會有一次滾動,空圖占據的大小若比真實圖片大,就會出現在這一次判斷中本來不會出現的圖,替換真實圖片重排后,高度減小,頂上來了。空圖如果比真實圖片小,就會加載還不應該出現的圖,lazyload的效果就打折扣了。PC可直接寫定px值,移動端需要使用相似比例的空圖。
其中上面要點2比較坑。
垂直Y方向直接監聽window的scroll即可,但水平X方向的多半是頁面內某個元素設定了一定區域並overflow:auto; 偏偏頁面內元素的scroll事件是不會冒泡的,事件的bubbles為false,即不可冒泡
默認的頁面滾動事件是可冒泡的,由document開始觸發,冒泡到window,事件的bubbles為true,即可冒泡,最坑的是獲取和設置scrollTop等值時,卻是使用 body 或 html 元素,水平有限,不理解為什么會是割裂開的。
對於這個問題,暫時沒想到好的解決方案,暫時用寫2次的方法解決,如lazyload(window),再lazyload('#container')監聽各自的事件
而判斷圖片是否出現時,剛開始我是使用elem.offsetTop對比scrollTop,后來發現offsetTop是相對最近的定位元素的,很容易就坑了。
一番尋覓后才發現 elem.getBoundingClientRect(),一番查資料后總結了這個方法的一些要點
elem.getBoundingClientRect() 獲取 元素相對於瀏覽器窗口的距離,會受到滾動的影響,實時獲取,translate,scale等transform屬性也會影響結果
getBoundingClientRect是DOM元素到瀏覽器可視范圍的距離(不包含文檔卷起的部分)。
該函數返回一個Object對象,該對象有6個屬性:top,lef,right,bottom,width,height;
這里的top、left和css中的理解很相似,width、height是元素自身的寬高,
但是right,bottom和css中的理解有點不一樣。right是指元素右邊界距窗口最左邊的距離,bottom是指元素下邊界距窗口最上面的距離。
踩完一些坑后,總算仿照PC端的jQuery插件完成了輪子,代碼如下
function lazyload(options){ var settings = { selector : 'img.lazy', container : window, threshold : 0, failurelimit : 0, dataAttribute : "data-original", }; if(typeof options == 'object'){ for(var key in options){ settings[key] = options[key] || settings[key]; } } var tId = null; var imgsArr = Array.prototype.slice.call(document.querySelectorAll(settings.selector)); function inViewport(elem, threshold){ var o = elem.getBoundingClientRect(); var pageWidth = document.documentElement.clientWidth; var pageHeight = document.documentElement.clientHeight; threshold = threshold || pageHeight/5; return o.left < pageWidth + threshold && o.top < pageHeight + threshold } function loadImg(){ clearTimeout(tId); tId = setTimeout(function(){ var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; var src = ''; var item = null; var counter = 0; if(imgsArr.length > 0){ for (var i = 0; i < imgsArr.length; i++) { item = imgsArr[i]; if( inViewport(item) ){ src = item.getAttribute(settings.dataAttribute); item.setAttribute('src', src); imgsArr.splice(i,1); i--; counter = 0; }else if( ++counter > settings.failurelimit ){ break; } }; }else { settings.container.removeEventListener('scroll',loadImg); } }, 100); } loadImg(); settings.container.addEventListener('scroll',loadImg); }
使用時和jQuery的lazyload插件類似,只是沒有了$,直接使用函數lazyload();默認配置如下
lazyload({ selector : 'img.lazy', // 選擇器 container : window, // 容器 threshold : 0, // 預留間距,即圖片距離屏幕還有一定距離就預先加載 failurelimit : 0, // failurelimit,值為數字.lazyload默認在找到第一張不在可見區域里的圖片時則不再繼續加載,但當HTML容器混亂的時候可能出現可見區域內圖片並沒加載出來的情況,failurelimit意在加載N張可見區域外的圖片,以避免出現這個問題. dataAttribute : "data-original", // 存放真實URL的屬性 });
以上實現還沒有測試兼容,純屬交流,歡迎指正
