原生 JS 實現(xiàn)最簡單的圖片懶加載

懶加載


什么是懶加載

懶加載其實就是延遲加載,是一種對網(wǎng)頁性能優(yōu)化的方式,比如當(dāng)訪問一個頁面的時候,優(yōu)先顯示可視區(qū)域的圖片而不一次性加載所有圖片,當(dāng)需要顯示的時候再發(fā)送圖片請求,避免打開網(wǎng)頁時加載過多資源。

什么時候用懶加載

當(dāng)頁面中需要一次性載入很多圖片的時候,往往都是需要用懶加載的。

懶加載原理

我們都知道HTML中的 <img>標(biāo)簽是代表文檔中的一個圖像。。說了個廢話。。

<img>標(biāo)簽有一個屬性是 src,用來表示圖像的URL,當(dāng)這個屬性的值不為空時,瀏覽器就會根據(jù)這個值發(fā)送請求。如果沒有 src屬性,就不會發(fā)送請求。

嗯?貌似這點可以利用一下?

我先不設(shè)置 src,需要的時候再設(shè)置?

nice,就是這樣。

我們先不給 <img>設(shè)置 src,把圖片真正的URL放在另一個屬性 data-src中,在需要的時候也就是圖片進(jìn)入可視區(qū)域的之前,將URL取出放到 src中。

實現(xiàn)


HTML結(jié)構(gòu)

<div class="container">

?<div class="img-area">

? ?<img class="my-photo" alt="loading" src="./img/img1.png">

?</div>

?<div class="img-area">

? ?<img class="my-photo" alt="loading" src="./img/img2.png">

?</div>

?<div class="img-area">

? ?<img class="my-photo" alt="loading" src="./img/img3.png">

?</div>

?<div class="img-area">

? ?<img class="my-photo" alt="loading" src="./img/img4.png">

?</div>

?<div class="img-area">

? ?<img class="my-photo" alt="loading" src="./img/img5.png">

?</div>

</div>

仔細(xì)觀察一下, <img>標(biāo)簽此時是沒有 src屬性的,只有 altdata-src屬性。

alt 屬性是一個必需的屬性,它規(guī)定在圖像無法顯示時的替代文本。 data-* 全局屬性:構(gòu)成一類名稱為自定義數(shù)據(jù)屬性的屬性,可以通過 HTMLElement.dataset來訪問。

如何判斷元素是否在可視區(qū)域

方法一

網(wǎng)上看到好多這種方法,稍微記錄一下。

通過?document.documentElement.clientHeight獲取屏幕可視窗口高度

通過?document.documentElement.scrollTop獲取瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動條滾動的距離

通過?element.offsetTop獲取元素相對于文檔頂部的距離

然后判斷②-③<①是否成立,如果成立,元素就在可視區(qū)域內(nèi)。

方法二(推薦)

通過 getBoundingClientRect()方法來獲取元素的大小以及位置,MDN上是這樣描述的:

The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.

這個方法返回一個名為 ClientRectDOMRect對象,包含了 toprightbottonleftwidthheight這些值。

MDN上有這樣一張圖:

可以看出返回的元素位置是相對于左上角而言的,而不是邊距。

我們思考一下,什么情況下圖片進(jìn)入可視區(qū)域。

假設(shè) constbound=el.getBoundingClientRect();來表示圖片到可視區(qū)域頂部距離; 并設(shè) constclientHeight=window.innerHeight;來表示可視區(qū)域的高度。

隨著滾動條的向下滾動, bound.top會越來越小,也就是圖片到可視區(qū)域頂部的距離越來越小,當(dāng) bound.top===clientHeight時,圖片的上沿應(yīng)該是位于可視區(qū)域下沿的位置的臨界點,再滾動一點點,圖片就會進(jìn)入可視區(qū)域。

也就是說,在 bound.top<=clientHeight時,圖片是在可視區(qū)域內(nèi)的。

我們這樣判斷:

function isInSight(el) {

?const bound = el.getBoundingClientRect();

?const clientHeight = window.innerHeight;

?//如果只考慮向下滾動加載

?//const clientWidth = window.innerWeight;

?return bound.top <= clientHeight + 100;

}

這里有個+100是為了提前加載。

加載圖片

頁面打開時需要對所有圖片進(jìn)行檢查,是否在可視區(qū)域內(nèi),如果是就加載。

function checkImgs() {

?const imgs = document.querySelectorAll('.my-photo');

?Array.from(imgs).forEach(el => {

? ?if (isInSight(el)) {

? ? ?loadImg(el);

? ?}

?})

}

function loadImg(el) {

?if (!el.src) {

? ?const source = el.dataset.src;

? ?el.src = source;

?}

}

這里應(yīng)該是有一個優(yōu)化的地方,設(shè)一個標(biāo)識符標(biāo)識已經(jīng)加載圖片的index,當(dāng)滾動條滾動時就不需要遍歷所有的圖片,只需要遍歷未加載的圖片即可。

函數(shù)節(jié)流

在類似于滾動條滾動等頻繁的DOM操作時,總會提到“函數(shù)節(jié)流、函數(shù)去抖”。

所謂的函數(shù)節(jié)流,也就是讓一個函數(shù)不要執(zhí)行的太頻繁,減少一些過快的調(diào)用來節(jié)流。

基本步驟:

獲取第一次觸發(fā)事件的時間戳

獲取第二次觸發(fā)事件的時間戳

時間差如果大于某個閾值就執(zhí)行事件,然后重置第一個時間

function throttle(fn, mustRun = 500) {

?const timer = null;

?let previous = null;

?return function() {

? ?const now = new Date();

? ?const context = this;

? ?const args = arguments;

? ?if (!previous){

? ? ?previous = now;

? ?}

? ?const remaining = now - previous;

? ?if (mustRun && remaining >= mustRun) {

? ? ?fn.apply(context, args);

? ? ?previous = now;

? ?}

?}

}

這里的 mustRun就是調(diào)用函數(shù)的時間間隔,無論多么頻繁的調(diào)用 fn,只有 remaining>=mustRunfn才能被執(zhí)行。

實驗


頁面打開時

可以看出此時僅僅是加載了img1和img2,其它的img都沒發(fā)送請求,看看此時的瀏覽器

第一張圖片是完整的呈現(xiàn)了,第二張圖片剛進(jìn)入可視區(qū)域,后面的就看不到了~

頁面滾動時

當(dāng)我向下滾動,此時瀏覽器是這樣

此時第二張圖片完全顯示了,而第三張圖片顯示了一點點,這時候我們看看請求情況

img3的請求發(fā)出來,而后面的請求還是沒發(fā)出~

全部載入時

當(dāng)滾動條滾到最底下時,全部請求都應(yīng)該是發(fā)出的,如圖

更新


方法三 IntersectionObserver

經(jīng)大佬提醒,發(fā)現(xiàn)了這個方法

先附上鏈接:

jjc大大:

https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10

阮一峰大大:

http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

API Sketch for Intersection Observers:

https://github.com/WICG/IntersectionObserver

IntersectionObserver可以自動觀察元素是否在視口內(nèi)。

var io = new IntersectionObserver(callback, option);

// 開始觀察

io.observe(document.getElementById('example'));

// 停止觀察

io.unobserve(element);

// 關(guān)閉觀察器

io.disconnect();

callback的參數(shù)是一個數(shù)組,每個數(shù)組都是一個 IntersectionObserverEntry對象,包括以下屬性:

屬性描述time可見性發(fā)生變化的時間,單位為毫秒rootBounds與getBoundingClientRect()方法的返回值一樣boundingClientRect目標(biāo)元素的矩形區(qū)域的信息intersectionRect目標(biāo)元素與視口(或根元素)的交叉區(qū)域的信息intersectionRatio目標(biāo)元素的可見比例,即intersectionRect占boundingClientRect的比例,完全可見時為1,完全不可見時小于等于0target被觀察的目標(biāo)元素,是一個 DOM 節(jié)點對象

我們需要用到 intersectionRatio來判斷是否在可視區(qū)域內(nèi),當(dāng) intersectionRatio>0&&intersectionRatio<=1即在可視區(qū)域內(nèi)。

代碼

function checkImgs() {

?const imgs = Array.from(document.querySelectorAll(".my-photo"));

?imgs.forEach(item => io.observe(item));

}

function loadImg(el) {

?if (!el.src) {

? ?const source = el.dataset.src;

? ?el.src = source;

?}

}

const io = new IntersectionObserver(ioes => {

?ioes.forEach(ioe => {

? ?const el = ioe.target;

? ?const intersectionRatio = ioe.intersectionRatio;

? ?if (intersectionRatio > 0 && intersectionRatio <= 1) {

? ? ?loadImg(el);

? ?}

? ?el.onload = el.onerror = () => io.unobserve(el);

?});

});


感興趣的小伙伴,可以關(guān)注公眾號【grain先森】,回復(fù)關(guān)鍵詞 “vue”,獲取更多資料,更多關(guān)鍵詞玩法期待你的探索~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載地址:https://segmentfault.com/a/1190000010744417懶加載什么是懶加載...
    秀逼閱讀 485評論 0 0
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,774評論 1 92
  • 在日常的抓取工作當(dāng)中,經(jīng)常看到網(wǎng)站為了減輕網(wǎng)頁加載所需資源對圖片等資源進(jìn)行延遲加載。那什么樣的頁面需要用到延遲加載...
    蘇敏閱讀 661評論 2 5
  • 什么是懶加載 懶加載其實就是延遲加載,是一種對網(wǎng)頁性能優(yōu)化的方式,比如當(dāng)訪問一個頁面的時候,優(yōu)先顯示可視區(qū)域的圖片...
    我向你奔閱讀 2,651評論 0 4
  • 轉(zhuǎn)載地址 http://hjingren.cn/2017/06/09/js%E5%AE%9E%E7%8E%B0%E...
    胡楊林_3b8d閱讀 393評論 0 0