圖片延遲載入策略(JavaScript)

lce_shou發表於2017-03-15

:文章最末尾有個人公眾號二維碼,會分享更多技術文章等,敬請關注

不管是在移動端開發還是在PC端開發,基本都會用到圖片的懶載入,這其中最大的原因就是為了保證頁面開啟的速度(正常情況下3秒之內如果首頁打不開,就已經算是死亡時間了)。

延遲載入的的基本做法一般是這樣的:

  • 首屏內容中的圖片:首先給對應的區域一張預設的圖片佔著位置(預設圖需要非常的小,一般可以維持在5kb以內),當首屏內容都載入完成後(或者也可以給一個延遲的時間),再開始載入真實的圖片。

  • 其他屏中的圖片:也是給一張預設的圖片佔位,當滾動條滾動到對應區域的時候,我們再開始載入真實的圖片。

擴充套件:對於資料的非同步載入,也是這樣的,並不會一次性把頁面中所有的資料都請求出來,正常情況是先請求前兩屏的資料,後面的資料先不請求,當頁面滾動到對應區域的時候,再重新請求資料,接著繫結渲染資料。

一、首屏單張圖片延遲載入

html

<div id="banner">
    <!--trueImg是當前IMG標籤的自定義屬性,儲存的是真實圖片的地址-->
    <img src="" trueImg="img/jd.jpg">
</div>複製程式碼

css

* {margin: 0; padding: 0; font-family: "\5FAE\8F6F\96C5\9ED1", Helvetica, sans-serif; font-size: 14px;}
img {display: block;border: none;}
#banner {margin: 10px auto; width: 350px; height: 200px; border: 1px solid green; background: url("img/default.gif") no-repeat center center #e1e1e1; }
#banner img {display: none; width: 100%;height: 100%;}複製程式碼

在設定樣式的時候,把預設的圖片設定給img外層的div,作為div的背景,因為要延遲載入,所以一開始給src的屬性值是空,這樣的話在IE瀏覽器當中會顯示一張碎圖,不美觀,所以我們讓其預設是隱藏的,當真實的圖片載入完成後,再顯示。

JavaScript
在沒有想其他情況的時候,我們可能會很簡單的就把JavaScript程式碼寫成以下這樣:

var banner = document.getElementById('banner') , imgFir = banner.getElementsByTagName('img')[0];

imgFir.src = imgFir.getAttribute('trueImg');
imgFir.style.display = 'block';複製程式碼

但是以上處理是不完整的:如果我們獲取的真實圖片地址是錯誤的,當賦值給IMG的src屬性的時候,不僅控制檯會報錯,而且頁面中會出現碎圖/叉子圖,影響視覺效果。所以我們獲取圖片的地址後,應該先驗證地址的有效性,是有效的才賦值,不是有效的是不進行賦值處理的。

var oImg = new Image; // 建立一個臨時的img標籤
oImg.src = imgFir.getAttribute('trueImg');
oImg.onload = function () { // 當圖片能夠正常載入
    imgFir.src = this.src;
    imgFir.style.display = 'block';
    oImg = null;
}複製程式碼

二、超過一屏的單張圖片延遲載入

圖片延遲載入策略(JavaScript)
超過一屏的單張圖片.png

A:圖片容器距離body的上偏移 + 容器的offsetHeight

B:瀏覽器一螢幕的高度 + 滾動條捲去的高度

所以判斷的條件就是:A < B 容器就已經完全的進入了視野,載入真實的圖片。

window.onscroll = function () {
    if (banner.isLoad) { // 已經載入過了
        return;
    }
    var A = banner.offsetHeight + utils.offset(banner).top;
    var B = utils.win('clientHeight') + utils.win('scrollTop');
    if (A < B) {
        console.log('ok');
        // 當條件成立,載入真實的圖片,第一次載入完成後,再讓頁面繼續滾動的過程中,A<B一直成立,又重新的執行了下面的操作,導致了重複給一個容器中的圖片進行載入,所以有下面的isLoad自定義屬性
        var oImg = new Image;
        oImg.src = imgFir.getAttribute('trueImg');
        oImg.onload = function () {
            imgFir.src = this.src;
            imgFir.style.display = 'block';
            oImg = null;
        };
        // 設定一個自定義屬性,告訴瀏覽器我已經把圖片載入完了(不管是否正常的載入,只要處理過一次以後都不再處理了)
        banner.isLoad = true;
    }
}複製程式碼

本例中用到的utils工具類中的offset方法和win方法:

function offset(curEle) {
    var disLeft = curEle.offsetLeft, disTop = curEle.offsetTop, par = curEle.offsetParent;
    while (par) {
        if (navigator.userAgent.indexOf("MSIE 8") === -1) {
            disLeft += par.clientLeft;
            disTop += par.clientTop;
        }
        disLeft += par.offsetLeft;
        disTop += par.offsetTop;
        par = par.offsetParent;
    }
    return {left: disLeft, top: disTop};
}

function win(attr, value) {
    if (typeof value === "undefined") {
        return document.documentElement[attr] || document.body[attr];
    }
    document.documentElement[attr] = value;
    document.body[attr] = value;
}複製程式碼

多張圖片延遲載入

本例為移動端的情況,有一個列表,超過螢幕好幾屏

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多張圖片的延遲載入</title>
    <!--做移動端響應式佈局頁面,我們必須要加META:viewport-->
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
    <style type="text/css">
        * {margin: 0; padding: 0; font-family: "\5FAE\8F6F\96C5\9ED1", Helvetica, sans-serif; font-size: 14px;}
        ul,li {
            list-style: none;
        }
        img {
            display: block;
            border: none;
        }
        /* 最外層的容器是不設定固定寬高的 */
        .news {
            padding: 10px;
        }

        .news li {
            position: relative;
            height: 60px;
            padding: 10px 0;
            border-bottom: 1px dashed #ccc;
        }
        .news li > div:nth-child(1) {
            position: absolute;
            top: 10px;
            left: 0;
            width: 75px;
            height: 60px;
            background: url("img/default.jpg") no-repeat center center;
            background-size: 100% 100%;
        }
        .news li > div:nth-child(1) img {
            width: 100%;
            height: 100%;
            display: none;
        }
        .news li > div:nth-child(2) {
            margin-left: 80px;
            height: 60px;
        }
        .news li > div:nth-child(2) h2 {
            height: 20px;
            line-height: 20px;
            /* 實現文字超出一行,自動裁切 */
            overflow: hidden;
            text-overflow: ellipsis; /*裁下去的步伐以三個省略號展示*/
            white-space: nowrap;
        }
        .news li > div:nth-child(2) p {
            height: 20px;
            line-height: 20px;
            font-size: 12px;
            color: darkgrey;
        }
    </style>
</head>
<body>

<ul class="news" id="news">
    <!--<li>-->
        <!--<div>-->
            <!--<img src="" trueImg="img/1.jpg" alt="">-->
        <!--</div>-->
        <!--<div>-->
            <!--<h2>AAAAAAAAAAAAAAAAAAAAAAAAAAAAA</h2>-->
            <!--<p>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB</p>-->
        <!--</div>-->
    <!--</li>-->
</ul>


<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript">
    var news = document.getElementById("news"), imgList = news.getElementsByTagName("img");

    // 1、獲取需要繫結的資料(Ajax)->json/newsList.txt中的JSON格式的字串
    var jsonData = null;
    ~ function () {
        var xhr = new XMLHttpRequest();
        //URL地址後面加的隨機數是在清除每一次請求資料時候(GET請求)產生的快取
        xhr.open("get", "json/newsList.txt?_=" + Math.random(), false);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
                var val = xhr.responseText;
                jsonData = utils.formatJSON(val);
            }
        };
        xhr.send(null);
    }();
    console.log(jsonData);

    //2、資料繫結->把jsonData中儲存的資料繫結在頁面中(字串拼接)
    ~function () {
        var str = '';
        if (jsonData) {
            for (var i = 0, len = jsonData.length; i < len; i++) {
                var curData = jsonData[i];
                str += '<li>';
                str += '<div><img src="" trueImg="' + curData["img"] + '"/></div>';
                str += '<div>';
                str += '<h2>' + curData["title"] + '</h2>';
                str += '<p>' + curData["desc"] + '</p>';
                str += '</div>';
                str += '</li>';
            }
        }
        news.innerHTML = str;
    }();

    // 3、圖片延遲載入
    //->我先編寫一個方法,實現單張圖片的延遲載入
    function lazyImg(curImg) {
        console.log('ok')
        var oImg = new Image;
        oImg.src = curImg.getAttribute("trueImg");
        oImg.onload = function () {
            curImg.src = this.src;
            curImg.style.display = "block";
            fadeIn(curImg);
            oImg = null;
        };
        curImg.isLoad = true;
    }

    //->實現漸現的效果
    function fadeIn(curImg) {
        var duration = 500, interval = 10, target = 1;
        var step = (target / duration) * interval;
        var timer = window.setInterval(function () {
            var curOp = utils.getCss(curImg, "opacity");
            if (curOp >= 1) {
                curImg.style.opacity = 1;
                window.clearInterval(timer);
                return;
            }
            curOp += step;
            curImg.style.opacity = curOp;
        }, interval);
    }

    //->迴圈處理每一張圖片
    function handleAllImg() {
        for (var i = 0, len = imgList.length; i < len; i++) {
            var curImg = imgList[i];

            //->當前的圖片處理過的話,就不需要在重新的進行處理了
            if (curImg.isLoad) {
                continue;
            }

            //->只有A<B的時候在進行處理:當前圖片是隱藏的,我們計算的A的值其實是計算它父親(容器)的值
            var curImgPar = curImg.parentNode;
            var A = utils.offset(curImgPar).top + curImgPar.offsetHeight, B = utils.win("clientHeight") + utils.win("scrollTop");
            if (A < B) {
                lazyImg(curImg);
            }
        }
    }

    //4、開始的時候(過500ms載入第一螢幕的圖片)、滾動條滾動的時候載入其它圖片
    window.setTimeout(handleAllImg, 500);
    window.onscroll = handleAllImg;
</script>
</body>
</html>複製程式碼

個人公眾號(icemanFE):分享更多的前端技術和生活感悟

圖片延遲載入策略(JavaScript)
個人公眾號

相關文章