圖片延遲加載3種實(shí)現(xiàn)方式

定義:延遲加載也稱為惰性加載揪垄,即在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖像穷吮。用戶滾動(dòng)到它們之前,視口外的圖像不會(huì)加載饥努。這與圖像預(yù)加載相反捡鱼,在長(zhǎng)網(wǎng)頁(yè)上使用延遲加載將使網(wǎng)頁(yè)加載更快。在某些情況下酷愧,它還可以幫助減少服務(wù)器負(fù)載驾诈。
舉個(gè)例子來(lái)說(shuō)明缠诅,當(dāng)打開淘寶首頁(yè)的時(shí)候,只有在瀏覽器窗口里的圖片才會(huì)被加載乍迄,當(dāng)你滾動(dòng)首頁(yè)向下滑的時(shí)候管引,進(jìn)入視口內(nèi)的圖片才會(huì)被加載,而其它從未進(jìn)入視口的圖像不會(huì)也不會(huì)加載闯两。

那么延遲加載有什么好處:

首先它能提升用戶的體驗(yàn)褥伴,試想一下,如果打開頁(yè)面的時(shí)候就將頁(yè)面上所有的圖片全部獲取加載漾狼,如果圖片數(shù)量較大噩翠,對(duì)于用戶來(lái)說(shuō)簡(jiǎn)直就是災(zāi)難,會(huì)出現(xiàn)卡頓現(xiàn)象邦投,影響用戶體驗(yàn)伤锚。
有選擇性地請(qǐng)求圖片,這樣能明顯減少了服務(wù)器的壓力和流量志衣,也能夠減小瀏覽器的負(fù)擔(dān)屯援。
那么下面就介紹延遲加載的三種實(shí)現(xiàn)方式:

第一種:

首先將頁(yè)面上的圖片的 src 屬性設(shè)為 loading.gif,而圖片的真實(shí)路徑則設(shè)置在 data-src 屬性中念脯,頁(yè)面滾動(dòng)的時(shí)候計(jì)算圖片的位置與滾動(dòng)的位置狞洋,當(dāng)圖片出現(xiàn)在瀏覽器視口內(nèi)時(shí),將圖片的 src 屬性設(shè)置為 data-src 的值绿店,這樣吉懊,就可以實(shí)現(xiàn)延遲加載。

下面是具體的實(shí)現(xiàn)代碼:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lazyload 1</title>
    <style>
        img {
        display: block;
        margin-bottom: 50px;
        height: 200px;
    }
    </style>
</head>
<body>
    <img src="images/loading.gif" data-src="images/1.png">
    <img src="images/loading.gif" data-src="images/2.png">
    <img src="images/loading.gif" data-src="images/3.png">
    <img src="images/loading.gif" data-src="images/4.png">
    <img src="images/loading.gif" data-src="images/5.png">
    <img src="images/loading.gif" data-src="images/6.png">
    <img src="images/loading.gif" data-src="images/7.png">
    <img src="images/loading.gif" data-src="images/8.png">
    <img src="images/loading.gif" data-src="images/9.png">
    <img src="images/loading.gif" data-src="images/10.png">
    <img src="images/loading.gif" data-src="images/11.png">
    <img src="images/loading.gif" data-src="images/12.png">
    <script>
        function lazyload() {
        var images = document.getElementsByTagName('img');
        var len    = images.length;
        var n      = 0;      //存儲(chǔ)圖片加載到的位置假勿,避免每次都從第一張圖片開始遍歷       
        return function() {
        var seeHeight = document.documentElement.clientHeight;
        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        for(var i = n; i < len; i++) {
            if(images[i].offsetTop < seeHeight + scrollTop) {
                if(images[i].getAttribute('src') === 'images/loading.gif') {
                 images[i].src = images[i].getAttribute('data-src');
            }
            n = n + 1;
             }
        }
        }
    }
    var loadImages = lazyload();
    loadImages();          //初始化首頁(yè)的頁(yè)面圖片
    window.addEventListener('scroll', loadImages, false);
    </script>
</body>
</html>

比較 image 的 offsetTop 與 seeHeight + scrollTop 的大小借嗽,當(dāng)小于時(shí)則說(shuō)明圖片已經(jīng)出現(xiàn)過(guò)在視口中,這時(shí)候繼續(xù)判斷圖片是否已經(jīng)替換過(guò)转培,如果沒(méi)有替換過(guò)恶导,則進(jìn)行替換。
你可以拷貝我的代碼去進(jìn)行實(shí)驗(yàn)浸须,但是請(qǐng)確保 HTML 同目錄下有 images 目錄并且含有 1~12.png 和 loading.gif惨寿。

需要提及的是變量 n 是用來(lái)保存已經(jīng)加載的圖片數(shù)量,避免每次都從第一張圖片開始遍歷删窒,提升性能裂垦。上面的代碼用到了 JS 閉包的知識(shí),如果你不太熟悉的話肌索,可以自行百度一下蕉拢。

第二種:

上面的代碼是沒(méi)什么問(wèn)題,但是性能偏差。如果直接將函數(shù)綁定在 scroll 事件上企量,當(dāng)頁(yè)面滾動(dòng)時(shí)测萎,函數(shù)會(huì)被高頻觸發(fā),這非常影響瀏覽器的性能届巩。我粗略地估計(jì)一下硅瞧,當(dāng)簡(jiǎn)單地滾動(dòng)一下頁(yè)面,函數(shù)至少觸發(fā)了十來(lái)次恕汇,這顯然是十分沒(méi)必要的腕唧。
所以在做事件綁定的時(shí)候,可以對(duì) lazyload 函數(shù)進(jìn)行函數(shù)節(jié)流(throttle)與函數(shù)去抖(debounce)處理瘾英。
這里我并不再另外介紹這兩種方案枣接,如果你想了解的話可以閱讀:JS魔法堂:函數(shù)節(jié)流(throttle)與函數(shù)去抖(debounce) - _肥仔John - 博客園

簡(jiǎn)單說(shuō)來(lái):

  • Debounce:一部電梯停在某一個(gè)樓層,當(dāng)有一個(gè)人進(jìn)來(lái)后缺谴,20秒后自動(dòng)關(guān)門但惶,這20秒的等待期間,又一個(gè)人按了電梯進(jìn)來(lái)湿蛔,這20秒又重新計(jì)算膀曾,直到電梯關(guān)門那一刻才算是響應(yīng)了事件。
  • Throttle:好比一臺(tái)自動(dòng)的飲料機(jī)阳啥,按拿鐵按鈕添谊,在出飲料的過(guò)程中,不管按多少這個(gè)按鈕察迟,都不會(huì)連續(xù)出飲料斩狱,中間按鈕的響應(yīng)會(huì)被忽略,必須要等這一杯的容量全部出完之后扎瓶,再按拿鐵按鈕才會(huì)出下一杯所踊。

下面就是經(jīng)過(guò) throttle 處理后的代碼:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lazyload 2</title>
    <style>
    img {
        display: block;
        margin-bottom: 50px;
        height: 200px;
    }
    </style>
</head>
<body>
    <img src="images/loading.gif" data-src="images/1.png">
    <img src="images/loading.gif" data-src="images/2.png">
    <img src="images/loading.gif" data-src="images/3.png">
    <img src="images/loading.gif" data-src="images/4.png">
    <img src="images/loading.gif" data-src="images/5.png">
    <img src="images/loading.gif" data-src="images/6.png">
    <img src="images/loading.gif" data-src="images/7.png">
    <img src="images/loading.gif" data-src="images/8.png">
    <img src="images/loading.gif" data-src="images/9.png">
    <img src="images/loading.gif" data-src="images/10.png">
    <img src="images/loading.gif" data-src="images/11.png">
    <img src="images/loading.gif" data-src="images/12.png">
    <script>
    function throttle(fn, delay, atleast) {
        var timeout = null,
        startTime = new Date();
        return function() {
        var curTime = new Date();
        clearTimeout(timeout);
        if(curTime - startTime >= atleast) {
            fn();
            startTime = curTime;
        }else {
            timeout = setTimeout(fn, delay);
        }
        }
    }
    function lazyload() {
        var images = document.getElementsByTagName('img');
        var len    = images.length;
        var n      = 0;      //存儲(chǔ)圖片加載到的位置,避免每次都從第一張圖片開始遍歷       
        return function() {
        var seeHeight = document.documentElement.clientHeight;
        var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        for(var i = n; i < len; i++) {
            if(images[i].offsetTop < seeHeight + scrollTop) {
                if(images[i].getAttribute('src') === 'images/loading.gif') {
                 images[i].src = images[i].getAttribute('data-src');
                }
            n = n + 1;
             }
        }
        }
    }
    var loadImages = lazyload();
    loadImages();          //初始化首頁(yè)的頁(yè)面圖片
    window.addEventListener('scroll', throttle(loadImages, 500, 1000), false);
    </script>
</body>
</html>

設(shè)置了 500ms 的延遲栗弟,和 1000ms 的間隔污筷,當(dāng)超過(guò) 1000ms 未觸發(fā)該函數(shù),則立即執(zhí)行該函數(shù)乍赫,不然則延遲 500ms 執(zhí)行該函數(shù)。

實(shí)現(xiàn)效果:可以看出有一定的延遲陆蟆。
參考鏈接:實(shí)現(xiàn)圖片懶加載(lazyload)

第三種: 使用 IntersectionObserver API

目前有一個(gè)新的 IntersectionObserver API雷厂,可以自動(dòng)"觀察"元素是否可見(jiàn),Chrome 51+ 已經(jīng)支持叠殷。

這里不過(guò)多介紹 IntersectionObserver API 的詳細(xì)使用改鲫,感興趣可以另外閱讀下面的文章:

IntersectionObserver API 使用教程
Intersection Observer API

實(shí)現(xiàn)代碼:簡(jiǎn)潔,但是瀏覽器尚未全部實(shí)現(xiàn)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lazyload 3</title>
    <style>
        img {
        display: block;
        margin-bottom: 50px;
        width: 800px;
        }
    </style>
</head>
<body>
    <img src="images/loading.gif" data-src="images/1.png">
    <img src="images/loading.gif" data-src="images/2.png">
    <img src="images/loading.gif" data-src="images/3.png">
    <img src="images/loading.gif" data-src="images/4.png">
    <img src="images/loading.gif" data-src="images/5.png">
    <img src="images/loading.gif" data-src="images/6.png">
    <img src="images/loading.gif" data-src="images/7.png">
    <img src="images/loading.gif" data-src="images/8.png">
    <img src="images/loading.gif" data-src="images/9.png">
    <img src="images/loading.gif" data-src="images/10.png">
    <img src="images/loading.gif" data-src="images/11.png">
    <img src="images/loading.gif" data-src="images/12.png">
    <script>
    function query(selector) {
        return Array.from(document.querySelectorAll(selector));
    }
    var io = new IntersectionObserver(function(items) {
        items.forEach(function(item) {
        var target = item.target;
        if(target.getAttribute('src') == 'images/loading.gif') {
            target.src = target.getAttribute('data-src');
        }
        })
    });
    query('img').forEach(function(item) {
        io.observe(item);
    });
    </script>
</body>
</html>

IntersectionObserver 傳入一個(gè)回調(diào)函數(shù)像棘,當(dāng)其觀察到元素集合出現(xiàn)時(shí)候稽亏,則會(huì)執(zhí)行該函數(shù)。
io.observe 即要觀察的元素缕题,要一個(gè)個(gè)添加才可以截歉。
io 管理的是一個(gè)數(shù)組,當(dāng)元素出現(xiàn)或消失的時(shí)候烟零,數(shù)組添加或刪除該元素瘪松,并且執(zhí)行該回調(diào)函數(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锨阿,一起剝皮案震驚了整個(gè)濱河市宵睦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌墅诡,老刑警劉巖壳嚎,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異末早,居然都是意外死亡诬辈,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門荐吉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)焙糟,“玉大人,你說(shuō)我怎么就攤上這事样屠〈┐椋” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵痪欲,是天一觀的道長(zhǎng)悦穿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)业踢,這世上最難降的妖魔是什么栗柒? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮知举,結(jié)果婚禮上瞬沦,老公的妹妹穿的比我還像新娘。我一直安慰自己雇锡,他們只是感情好逛钻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锰提,像睡著了一般曙痘。 火紅的嫁衣襯著肌膚如雪芳悲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天边坤,我揣著相機(jī)與錄音名扛,去河邊找鬼。 笑死茧痒,一個(gè)胖子當(dāng)著我的面吹牛肮韧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播文黎,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惹苗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了耸峭?” 一聲冷哼從身側(cè)響起桩蓉,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎劳闹,沒(méi)想到半個(gè)月后院究,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡本涕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年业汰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菩颖。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡样漆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晦闰,到底是詐尸還是另有隱情放祟,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布呻右,位于F島的核電站跪妥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏声滥。R本人自食惡果不足惜眉撵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望落塑。 院中可真熱鬧纽疟,春花似錦、人聲如沸芜赌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缠沈。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洲愤,已是汗流浹背颓芭。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柬赐,地道東北人亡问。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肛宋,于是被迫代替她去往敵國(guó)和親州藕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354