「按需加載」的應用

按需加載是前端性能優化中的一項重要措施,按需加載是如何定義的呢?顧名思義,指的是當用戶觸發了動做時才加載對應的功能。觸發的動做,是要看具體的業務場景而言,包括但不限於如下幾個狀況:鼠標點擊、輸入文字、拉動滾動條,鼠標移動、窗口大小更改等。加載的文件,能夠是JS、圖片、CSS、HTML等。後面將會詳細介紹「按需」的理解。html

按需解析HTML前端

按需解析HTML,就是頁面一開始不解析HTML,根據須要來解析HTML。解析HTML都是須要必定時間,特別是HTML中包含有img標籤、引用了背景圖片時,若是一開始就解析,那麼勢必會增長請求數。常見的有對話框、拉菜單、多標籤的內容展現等,這些一開始是不須要解析,能夠按需解析。實現按需解析,首先用<script type=」text/x-template」>html</sccript> 這個標籤來對忽略對HTML的解析。而後根據觸發的動做,script裏面的HTML獲取出來,填充到對應的節點中node

示例代碼以下:web

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>按需解析HTML</title>
</head>
<body>
<script type="text/x-template" id="suc_subscription">
    <!--假設這裏的樣式box-dytz 引用了一張背景圖--->
    <div>
    <!--這裏暫且用這張圖片做爲測試,實際中,你們能夠替換爲任何圖片-->
    <img src="http://tid.tenpay.com/wp-content/uploads/2012/12/按需加載.jpg" />
    </div>
</script>
<div id="success_dilog"></div>
<input type="button" value="點我展現HTML"  onclick="showHTML()"  />
<script>
    function showHTML(){
        document.getElementById('success_dilog').innerHTML =  document.getElementById('suc_subscription').innerHTML;
    }
</script>
</body>
</html>

咱們一塊兒來看下demo,當運行demo並抓包發現:當頁面加載結束時,並無看到圖片的請求;當點「點我展現HTML」按鈕時,經過抓包發現有圖片請求。api

曾經作個demo並通過測試發現,若是是直接解析HTML(不包含有請求CSS圖片和img標籤),耗費的時間要比用<script type=」text/x-template」>html</script>大約慢1-2倍,若是是還包括請求有CSS圖片、img標籤,請求鏈接數將會更多,可見按需解析HTML,對性能提高仍是有必定效果。數組

按需加載圖片性能優化

按需加載圖片,就是讓圖片默認開始不加載,並且在接近可視區域範圍時,再進行加載。也稱之爲懶惰加載。你們都知道,圖片一會兒所有都加載,請求的次數將會增長,勢必影響性能。併發

先來看下懶惰加載的實現原理。它的觸發動做是:當滾動條拉動到某個位置時,即將進入可視範圍的圖片須要加載。實現的過程分爲下面幾個步驟:app

  •  生成<img data-src="http://tid.tenpay.com/」url」>標籤時,用data-src來保存圖片地址;
  • 記錄的圖片data-src都保存到數組裏;
  • 對滾動條進行事件綁定,假設綁定的函數爲function lazyload(){};
  • 在函數lazyload中,按照下面思路實現:計算圖片的Y座標,並計算可視區域的高度height,當Y小於等於(height+ scrollTop)時,圖片的src的值用data-src的來替換,從而來實現圖片的按需加載;

下面看一個示例代碼:框架

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>圖片按需加載</title>
</head>
<body>
    <style>
        li {float:left;width:200px;}
    </style>
    <div style="widh:100%;height:1200px;border:1px solid #000">這裏是空白的內容,高度爲900像素,目的是方便出現滾動條</div>
    <ul style="width:600px;">
        <li> <img width="158" height="158" data-src="http://pic6.nipic.com/20100423/4537140_133035029164_2.jpg"  /> </li>
        <li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223245.jpg"  /> </li>
        <li> <img width="158" height="158" data-src="http://www.bz55.com/uploads/allimg/100729/095KS455-0.jpg"  /> </li>
        <li> <img width="158" height="158" data-src="http://www.hyrc.cn/upfile/3/200611/1123539053c7e.jpg"/> </li>
        <li> <img width="158" height="158" data-src="http://www.mjjq.com/blog/photos/Image/mjjq-photos-903.jpg" /> </li>
        <li> <img width="158" height="158" data-src="http://c.cncnimg.cn/000/954/1416_2_b.jpg"  /> </li>
        <li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223231.jpg" /> </li>
        <li> <img width="158" height="158" data-src="http://www.mjjq.com/pic/20070530/20070530043314558.jpg" /> </li>
    </ul>
    <script>
        var API = {   
            /**
              * 兼容Mozilla(attachEvent)和IE(addEventListener)的on事件
              * @param {String} element DOM對象 例如:window,li等
              * @param {String} type on事件類型,例如:onclick,onscroll等
              * @param {Function} handler 回調事件
              */
            on: function(element, type, handler) {
                return element.addEventListener ? element.addEventListener(type, handler, false) : element.attachEvent('on' + type, handler)
            },   
            /**
              * 爲對象綁定事件
              * @param {Object} object 對象
              * @param {Function} handler 回調事件
              */
            bind: function(object, handler) {
                return function() {
                    return handler.apply(object, arguments)
                }
            },   
            /**
              * 元素在頁面中X軸的位置
              * @param {String} element DOM對象 例如:window,li等
              */
            pageX: function(El) {
                var left = 0;
                 do {
                    left += El.offsetLeft;

                } while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY');
                return left;

            },   
            /**
              * 元素在頁面中Y軸的位置
              * @param {String} element DOM對象 例如:window,li等
              */
            pageY: function(El) {
                var top = 0;
                 do {
                    top += El.offsetTop;

                } while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY');
                return top;

            },   
            /**
              * 判斷圖片是否已加載
              * @param {String} element DOM對象 例如:window,li等
              * @param {String} className 樣式名稱
              * @return {Boolean} 布爾值
              */
            hasClass: function(element, className) {
                return new RegExp('(^|\\s)' + className + '(\\s|$)').test(element.className)
            },   
            /**
              * 獲取或者設置當前元素的屬性值
              * @param {String} element DOM對象 例如:window,li等
              * @param {String} attr 屬性
              * @param {String} (value) 屬性的值,此參數若是沒有那麼就是獲取屬性值,此參數若是存在那麼就是設置屬性值
              */
            attr: function(element, attr, value) {
                 if (arguments.length == 2) {
                    return element.attributes[attr] ? element.attributes[attr].nodeValue : undefined
                }
                else if (arguments.length == 3) {
                    element.setAttribute(attr, value)
                }
            }
        };

        /**
          * 按需加載
          * @param {String} obj 圖片區域元素ID
          */
        function lazyload(obj) {
            this.lazy = typeof obj === 'string' ? document.getElementById(obj) : document.getElementsByTagName('body')[0];
            this.aImg = this.lazy.getElementsByTagName('img');
            this.fnLoad = API.bind(this, this.load);
            this.load();
            API.on(window, 'scroll', this.fnLoad);
            API.on(window, 'resize', this.fnLoad);

        }
        lazyload.prototype = {

            /**
              * 執行按需加載圖片,並將已加載的圖片標記爲已加載
              * @return 無
              */
            load: function() {
                var iScrollTop = document.documentElement.scrollTop || document.body.scrollTop;
                // 屏幕上邊緣
                var iClientHeight = document.documentElement.clientHeight + iScrollTop;
                // 屏幕下邊緣
                var i = 0;
                var aParent = [];
                var oParent = null;
                var iTop = 0;
                var iBottom = 0;
                var aNotLoaded = this.loaded(0);
                 if (this.loaded(1).length != this.aImg.length) {
                    var notLoadedLen = aNotLoaded.length;
                     for (i = 0; i < notLoadedLen; i++) {
                        iTop = API.pageY(aNotLoaded[i]) - 200;
                        iBottom = API.pageY(aNotLoaded[i]) + aNotLoaded[i].offsetHeight + 200;
                        var isTopArea = (iTop > iScrollTop && iTop < iClientHeight) ? true : false;
                        var isBottomArea = (iBottom > iScrollTop && iBottom < iClientHeight) ? true : false;
                         if (isTopArea || isBottomArea) {                   
                            // 把預存在自定義屬性中的真實圖片地址賦給src
                            aNotLoaded[i].src = API.attr(aNotLoaded[i], 'data-src') || aNotLoaded[i].src;
                             if (!API.hasClass(aNotLoaded[i], 'loaded')) {
                                 if ('' != aNotLoaded[i].className) {
                                    aNotLoaded[i].className = aNotLoaded[i].className.concat(" loaded");

                                }
                                else {
                                    aNotLoaded[i].className = 'loaded';

                                }
                            }
                        }
                    }
                }
            },

            /**
              * 已加載或者未加載的圖片數組
              * @param {Number} status 圖片是否已加載的狀態,0表明未加載,1表明已加載
              * @return Array 返回已加載或者未加載的圖片數組
              */
            loaded: function(status) {
                var array = [];
                var i = 0;
                 for (i = 0; i < this.aImg.length; i++) {
                    var hasClass = API.hasClass(this.aImg[i], 'loaded');
                     if (!status) {
                        if (!hasClass)
                            array.push(this.aImg[i])
                    }
                    if (status) {
                        if (hasClass)
                            array.push(this.aImg[i])
                    }
                }
                return array;       
            }
        };
        // 按需加載初始化
        API.on(window, 'load', function () {new lazyload()});
    </script>
</body>
</html>

運行上述的示例代碼,並抓包會發現:一開始並無看到圖片的請求,但當拉動滾動條到頁面下面時,將會看到圖片發送請求。目前不少框架都已經支持圖片的懶惰加載,平時在開發中,你們能夠對圖片實現懶惰加載,這是有效提高性能的一個方法,特別是網頁圖片比較多時,更加應該使用該方法。

按需加載除了上述場景外,還有更多的場景。以下圖:

tab

頁面一開始,加載的是「所有」標籤裏面的內容,但在點擊「指定商品折扣券」標籤時,纔去加載對應的圖片。實現思路以下:

  •  生成<img data-src="http://tid.tenpay.com/」url」>標籤時,用data-src來保存圖片地址;
  •  在點擊標籤事件時,獲取全部圖片,圖片的src的值用data-src的來替換;

示例代碼以下:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>標籤按需加載</title>
</head>

<body>
    <style>
        ul li {width:200px;height:30px;float:left; list-style:none; text-align:center;}
        .on{border-top:1px solid #3FF;border-left:1px solid #3FF;border-right:1px solid #3FF; }
        .hide{display:none;}
    </style>
    <ul>
        <li>所有</li>
        <li id="viewTsb1" onclick="showTabContent()">指定商品折扣券</li>
    </ul>
    <div style="width:800px;height:500px;clear:both;">
        <div id="tab1" style="height:25px; line-height:25px; margin:50px 0 0 40px">所有標籤應該展現全部內容</div>
        <div id="tab2">
             <img width="158" height="158" data-src="http://img2.114msn.com/jindian/20081071153761308.jpg"  />
             <img width="158" height="158" data-src="http://www.mjjq.com/blog/photos/Image/mjjq-photos-900.jpg"  />
         </div>
    </div>
    <script>
        var isLoadedImg = false;
        function showTabContent(){ 
            if(isLoadedImg){
                return;
            }
            var elem = document.getElementById("tab2");
            document.getElementById("tab1").className="hide";
            elem.className="";
            var arrImage = elem.getElementsByTagName("img");
            var l = arrImage.length;
            for(var i=0;i<l;i++){
                arrImage[i].src = arrImage[i].getAttribute('data-src');
            }
            isLoadedImg = true;
            //todo 更改標籤狀態
        }
    </script>
</body>
</html>

運行上述代碼並抓包並發現:一開始沒有看到有圖片的請求,但點擊「指定商品折扣券」標籤時,看到有圖片的請求發送。須要注意的是,爲了確保體驗,首屏的圖片不建議懶惰加載,而應該直接展現出來;避免一開始用戶就沒法看到圖片,在IE下看到一個虛線框,這樣體驗反而很差。

按需執行JS

按需執行JS和懶惰加載圖片比較相似。當打開網頁時,若是等全部JS都加載並執行完畢,再把界面呈現給用戶,這樣總體上性能會比較慢,體驗也不友好。就是當某個動做觸發後,再執行相應的JS,以便來渲染界面。按需執行JS,能夠應用在下列場景:執行一些耗時比較久的JS代碼,或執行JS後,須要加載比較多圖片、加載iframe、加載廣告等。在一些webapp的應用中,或比較複雜的頁面時,更加應該使用這種方法。

實現思路和按需加載比較相似:

  •  對滾動條進行事件綁定,假設綁定的函數爲function lazyExecuteJS(){};
  •  在函數lazyExecuteJS中,按照下面思路實現:選擇一個元素做爲參照物,當滾動條即將靠近時該元素位置,開始執行對應的JS,從而實現對界面的渲染;

示例代碼以下(以YUI3框架爲例):

首先下載最近封裝的異步滾動條加載組件:Y.asyncScrollLoader,而後運行下面的代碼(須要把頁面和Y.asyncScrollLoader.js 放在同一個目錄):

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>按需執行JS</title>
</head>
<script src="http://yui.yahooapis.com/3.2.0/build/yui/yui-debug.js"></script>
<body>
    <div style="height:1500px"><span style="">向下拖動滾動條,拉到廣告條的位置,將會自動加載廣告的內容!</span></div>
    <div id="ADContent">這裏是廣告的內容,將會用JS進行填充</div>
    <script>
        YUI({modules:{'asyncScrollLoader': { 
            fullpath: 'Y.asyncScrollLoader.js', 
                type: 'js', 
                requires:['widget'] 
            } 
        }}).use('widget','asyncScrollLoader', "node", function(Y){
            var  loadAD = function(){
                //這裏能夠用簡單的代碼代替,實際項目中,能夠執行任何JS代碼,如請求CGI,或者廣告數據,而後再填充到頁面上
                var html = '<div><img src="/imgr?src=http%3A%2F%2Ft2.baidu.com%2Fit%2Fu%3D2027994158%2C3223530939%26amp%3Bfm%3D23%26amp%3Bgp%3D0.jpg&r=http%3A%2F%2Ftid.tenpay.com%2F%3Fp%3D4085" alt="" /><span>哈哈,我也是動態加載進來的,能夠從html結構或抓包看出\效果哈!</span><\/div>'
                Y.one('#ADContent').set('innerHTML',html);
            }

            var cfg = { 
                'elementName' : 'div', 
                'className'     : 'lazy-load', 
                'contentAttribute' : '' ,
                'foldDistance': 10,
                'obCallback' : {
                    'funName' : loadAD,
                    'argument' : [],
                    'context' : this
                }
            };

           new Y.asyncScrollLoader(cfg).renderer(); 
        });      
    </script>
</body>
</html>

運行上述代碼並抓包發現:打開頁面時,是不沒有看到有對應的圖片請求,但當滾動條拉到必定位置時,loadAD的函數被執行。

按需加載JS

JavaScript無非就是script標籤引入頁面,但當項目愈來愈大的時候,單頁面引入N個js顯然不行,合併爲單個文件減小了請求數,但請求的文件體積卻很大。這時候比較合理的作法就是按需加載。按需加載和按需執行JS比較相似,只不過要執行的JS變成了固定的「實現加載JS」的代碼。按需加載實現的思路以下:

  • 對滾動條進行事件綁定,假設綁定的函數爲function lazyLoadJS(){};
  • 在函數lazyLoadJS中,按照下面思路實現:選擇一個元素做爲參照物,當滾動條即將靠近時該元素位置,開始執行加載對應JS;
  •  在JS加載完畢後,開始執行相應的函數來渲染界面;
  • 在實際項目中,能夠根據須要設置一個目標距離,好比還有200像素該元素即將進入可視區域;按需加載JS和按需執行JS比較相似,這裏就再也不單獨提供示例代碼了;你們能夠在按需執行JS的中示例中,把loadAD函數更改成動態加載JS便可;

分屏展現

當一個網頁比較長,有好幾個屏幕,並且加載了大量的圖片、廣告等資源文件時,分屏展現,可提高頁面性能和用戶體驗。其實分屏展現也能夠從按需加載的的角度來看待,默認是加載第一屏幕的內容,當滾動條拉動即將到達下一個屏幕時,再開始渲染下個屏的內容。換言之,是把圖片、背景圖片、HTML一塊兒按需加載,一開始不對HTML進行解析,那麼背景圖、img圖片也不會進行加載。

分屏展現的思路以下:

  •  根據具體業務狀況,收集主流最大的分辨率的高度;假設這裏是用960px;
  •  按照這個高度進行分屏,依次把下一個屏幕內的HTML用<textarea>HTML</’textarea> 來表示;
  •  爲了讓頁面的高度不變,須要讓textarea佔據必定的頁面空間,也就是讓頁面出現對應的滾動條;所以須要指定樣式visility:hidden,並指定它的高度和寬度。
  • 利用上述講的按需執行JS,把<textarea>HTML</’textarea>裏面的HTML代碼提取出來,從新填充到textarea的父節點上,即可實現解析對應HTML,從而實現分屏展現。

示例代碼以下(須要把頁面和Y.asyncScrollLoader.js 放在同一個目錄):

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>網頁分屏展現</title>
</head>
<style>
    .class2 {
        visibility:hidden;
        widh:100%;
        height:300px;
    }
</style>
<script src="http://yui.yahooapis.com/3.2.0/build/yui/yui-debug.js"></script>
<body>
<div style="height:1500px"><span style="">向下拖動滾動條,美圖在下面!</span></div>
<textarea>
    <div>
        <img src="http://travel.shangdu.com/liu/uploads/allimg/090407/1521221.jpg" alt="" /><span style="font-size:16px;color:red;">我是動態加載進來的,能夠從html結構或抓包看出效果哈!</span>
    </div>
</textarea>
<div style="height:800px"><span style="">下面繼續上美圖!</span></div>
<textarea>
    <div>
        <img src="http://download.99sucai.com/upload/images/201006042009245.jpg"  alt="" /><span>哈哈,我也是動態加載進來的,能夠從html結構或抓包看出效果哈!</span>
    </div>
</textarea>
<script>
 YUI({modules:{'asyncScrollLoader': { 
     fullpath: 'Y.asyncScrollLoader.js', 
     type: 'js', 
     requires:['widget'] 
     } 
 }}).use('widget','asyncScrollLoader', "node", function(Y){
     var cfg = { 
        'elementName' : 'textarea', 
        'className'     : 'class2', 
        'contentAttribute' : 'value' ,
        'foldDistance': 10           
    };        
    new Y.asyncScrollLoader(cfg).renderer(); 
 });      
</script>
</body>
</html>

運行上面代碼並抓包發現:在默認首屏,並無去解析textarea裏面的代碼,但當拉動滾動條到必定位置時,textarea裏面的HTML依次被解析,從而實現了網頁分屏展現。上述的示例代碼,能夠在這裏下載來查看: 示例代碼

使用「按需加載」進行性能優化時,須要合理選擇觸發的動做。「按需加載」的最大優點在於減小了沒必要要的資源請求,節省流量,真正實現「按需所取」。可是「按需加載」自己若是使用不當也會影響用戶體驗,由於「按需加載」的時機在用戶觸發某動做以後,若是用戶的網速比較慢的話,加載腳本或執行腳本可能須要等候較長的時間,而用戶則不得不爲此付出代價。所以,若是要使用「按需加載」則須要選擇正確的觸發動做,若是是根據滾動條來觸發,可考慮一個目標距離,假設目標距離還有200像素即將進入可視區域,則就開始加載,而不是等到進入了可視區域才加載。以上所講的各類「按需加載」類型,均可以封裝成相應的組件,而後就能夠在項目中進行應用。

 

注:轉自「按需加載」的應用, 感謝原做者分享。

 

 

博主其餘方式推薦:lazyload 

相關文章
相關標籤/搜索