圖片懶加載(滾動加載)原理

一、什麼是圖片懶加載?

即在頁面載入的時候將頁面上的img標籤的src指向一個小圖片,把真實地址存放在一個自定義屬性中,這裏我用data-src來存放,如下。

<img src="loading.gif" data-src="http://xxx.ooo.com" />

然後將頁面img標籤獲取並保存,開啓一個定時器,遍歷保存的img標籤,判斷其位置是否出現在了可視區域內。如果出現在可視區域了那麼就把真實的src地址給賦值上。
並且從數組中刪除,避免重複判斷。我們可以獲取當前img的相對於文檔頂的偏移距離減去scrollTop的距離,然後和瀏覽器窗口高度在進行比較,如果小於瀏覽器窗口則出現在了可視區域內了,反之,則沒有。


二、爲什要使用這個技術?

對於圖片過多的頁面,爲了加速頁面加載速度,比如一個頁面中有很多圖片,如淘寶、京東首頁等等,如果一上來就發送這麼多請求,頁面加載就會很漫長,如果js文件都放在了文檔的底部,恰巧頁面的頭部又依賴這個js文件,那就不好辦了。更爲要命的是:一上來就發送百八十個請求,服務器可能就吃不消了(不是隻有一兩個人在訪問這個頁面)。
所以很多時候我們需要將頁面內未出現在可視區域內的圖片先不做加載, 等到滾動到可視區域後再去加載。
這樣子對於頁面加載性能上會有很大的提升,也提高了用戶體驗。


三、實現


1)屏幕可視窗口大小:對應於圖中1、2位置處

    原生方法:window.innerHeight 標準瀏覽器及IE9+ || document.documentElement.clientHeight 標準瀏覽器及低版本IE標準模式 ||

           document.body.clientHeight 低版本混雜模式

       jQuery方法: $(window).height() 

  2)瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動條滾動的距離:也就是圖中3、4處對應的位置;

    原生方法:window.pagYoffset——IE9+及標準瀏覽器 || document.documentElement.scrollTop 兼容ie低版本的標準模式 ||

         document.body.scrollTop 兼容混雜模式;

        jQuery方法:$(document).scrollTop(); 

  3)獲取元素的尺寸:對應於圖中5、6位置處;左邊jquery方法,右邊原生方法

    $(o).width() = o.style.width; 

    $(o).innerWidth() = o.style.width+o.style.padding;

    $(o).outerWidth() = o.offsetWidth = o.style.width+o.style.padding+o.style.border;

    $(o).outerWidth(true) = o.style.width+o.style.padding+o.style.border+o.style.margin;

    注意:要使用原生的style.xxx方法獲取屬性,這個元素必須已經有內嵌的樣式,如<div style="...."></div>;

    如果原先是通過外部或內部樣式表定義css樣式,必須使用o.currentStyle[xxx] || document.defaultView.getComputedStyle(0)[xxx]來獲取樣式值

  4)獲取元素的位置信息:對應與圖中7、8位置處

    1)返回元素相對於文檔document頂部、左邊的距離;

    jQuery:$(o).offset().top元素距離文檔頂的距離,$(o).offset().left元素距離文檔左邊緣的距離

    原生:getoffsetTop(),高程上有具體說明,這邊就忽略了;

      順便提一下返回元素相對於第一個以定位的父元素的偏移距離,注意與上面偏移距的區別;

        jQuery:position()返回一個對象,$(o).position().left = style.left,$(o).position().top = style.top;


5)知道如何獲取元素尺寸、偏移距離後,接下來一個問題就是:如何判斷某個元素進入或者即將進入可視窗口區域?

    (1)外面最大的框爲實際頁面的大小,中間淺藍色的框代表父元素的大小,對象1~8代表元素位於頁面上的實際位置;以水平方向來做如下說明!

    (2)對象8左邊界相對於頁面左邊界的偏移距離(offsetLeft)大於父元素右邊界相對於頁面左邊界的距離,此時可判讀元素位於父元素之外;

    (3)對象7左邊界跨過了父元素右邊界,此時:對象7左邊界相對於頁面左邊界的偏移距離(offsetLeft)小於 父元素右邊界相對於

      頁面左邊界的距離,因此對象7就進入了父元素可視區;

    (4)在對象6的位置處,對象5的右邊界與頁面左邊界的距離 大於 父元素左邊界與頁面左邊界的距離;

    (5)在對象5位置處時,對象5的右邊界與頁面左邊界的距離 小於 父元素左邊界與頁面左邊界的距離;此時,可判斷元素處於父元素可視區外;

      (6)因此水平方向必須買足兩個條件,才能說明元素位於父元素的可視區內;同理垂直方向也必須滿足兩個條件。


四、擴展爲jquery插件

  使用方法:$("selector").scrollLoad();

(function($) {
    $.fn.scrollLoading = function(options) {
        var defaults = {
            // 在html標籤中存放的屬性名稱;
            attr: "data-url",
            // 父元素默認爲window
            container: window,
            callback: $.noop
        };
        // 不管有沒有傳入參數,先合併再說;
        var params = $.extend({}, defaults, options || {});
        // 把父元素轉爲jquery對象;
        var container = $(params.container);
        // 新建一個數組,然後調用each方法,用於存儲每個dom對象相關的數據;
        params.cache = [];
        $(this).each(function() {
            // 取出jquery對象中每個dom對象的節點類型,取出每個dom對象上設置的圖片路徑
            var node = this.nodeName.toLowerCase(), url = $(this).attr(params["attr"]);
            //重組,把每個dom對象上的屬性存爲一個對象;
            var data = {
                obj: $(this),
                tag: node,
                url: url
            };
            // 把這個對象加到一個數組中;
            params.cache.push(data);
        });

        var callback = function(call) {
            if ($.isFunction(params.callback)) {
                params.callback.call(call);
            }
        };
        
        //每次觸發滾動事件時,對每個dom元素與container元素進行位置判斷,如果滿足條件,就把路徑賦予這個dom元素!
        var loading = function() {
            // 獲取父元素的高度
            var contHeight = container.outerHeight();
            var contWidth = container.outerWidth();

            // 獲取父元素相對於文檔頁頂部的距離,這邊要注意了,分爲以下兩種情況;
            if (container.get(0) === window) {
                // 第一種情況父元素爲window,獲取瀏覽器滾動條已滾動的距離;$(window)沒有offset()方法;
                var contop = $(window).scrollTop();
                var conleft = $(window).scrollLeft();
            } else {
                // 第二種情況父元素爲非window元素,獲取它的滾動條滾動的距離;
                var contop = container.offset().top;
                var conleft = container.offset().left;
            }

            $.each(params.cache, function(i, data) {
                var o = data.obj, tag = data.tag, url = data.url, post, posb, posl, posr;
                if (o) {
                    //對象頂部與文檔頂部之間的距離,如果它小於父元素底部與文檔頂部的距離,則說明垂直方向上已經進入可視區域了;
                    post = o.offset().top - (contop + contHeight);
                    //對象底部與文檔頂部之間的距離,如果它大於父元素頂部與文檔頂部的距離,則說明垂直方向上已經進入可視區域了;
                    posb = o.offset().top + o.height() - contop;

                    // 水平方向上同理;
                    posl = o.offset().left - (conleft + contWidth);
                    posr = o.offset().left + o.width() - conleft;

                    // 只有當這個對象是可視的,並且這四個條件都滿足時,才能給這個對象賦予圖片路徑;
                    if ( o.is(':visible') && (post < 0 && posb > 0) && (posl < 0 && posr > 0) ) {
                        if (url) {
                            //在瀏覽器窗口內
                            if (tag === "img") {
                                //設置圖片src
                                callback(o.attr("src", url));
                            } else {
                                // 設置除img之外元素的背景url
                                callback(o.css("background-image", "url("+ url +")"));
                            }
                        } else {
                            // 無地址,直接觸發回調
                            callback(o);
                        }
                        // 給對象設置完圖片路徑之後,把params.cache中的對象給清除掉;對象再進入可視區,就不再進行重複設置了;
                        data.obj = null;
                    }
                }
            });
        };
        //加載完畢即執行
        loading();
        //滾動執行
        container.bind("scroll", loading);
    };
})(jQuery);








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章