Demo地址:http://axuebin.com/lazyload
照片都是自己拍的哦~
懶載入
什麼是懶載入
懶載入其實就是延遲載入,是一種對網頁效能優化的方式,比如當訪問一個頁面的時候,優先顯示可視區域的圖片而不一次性載入所有圖片,當需要顯示的時候再傳送圖片請求,避免開啟網頁時載入過多資源。
什麼時候用懶載入
當頁面中需要一次性載入很多圖片的時候,往往都是需要用懶載入的。
懶載入原理
我們都知道HTML中的標籤是代表文件中的一個影象。。說了個廢話。。
標籤有一個屬性是
src
,用來表示影象的URL,當這個屬性的值不為空時,瀏覽器就會根據這個值傳送請求。如果沒有src
屬性,就不會傳送請求。
嗯?貌似這點可以利用一下?
我先不設定src
,需要的時候再設定?
nice,就是這樣。
我們先不給設定
src
,把圖片真正的URL放在另一個屬性data-src
中,在需要的時候也就是圖片進入可視區域的之前,將URL取出放到src
中。
實現
HTML結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<div class="container"> <div class="img-area"> <img class="my-photo" alt="loading" data-src="./img/img1.png"> </div> <div class="img-area"> <img class="my-photo" alt="loading" data-src="./img/img2.png"> </div> <div class="img-area"> <img class="my-photo" alt="loading" data-src="./img/img3.png"> </div> <div class="img-area"> <img class="my-photo" alt="loading" data-src="./img/img4.png"> </div> <div class="img-area"> <img class="my-photo" alt="loading" data-src="./img/img5.png"> </div> </div> |
仔細觀察一下,標籤此時是沒有
src
屬性的,只有alt
和data-src
屬性。
alt 屬性是一個必需的屬性,它規定在影象無法顯示時的替代文字。
data-* 全域性屬性:構成一類名稱為自定義資料屬性的屬性,可以通過HTMLElement.dataset
來訪問。
如何判斷元素是否在可視區域
方法一
網上看到好多這種方法,稍微記錄一下。
- 通過
document.documentElement.clientHeight
獲取螢幕可視視窗高度 - 通過
element.offsetTop
獲取元素相對於文件頂部的距離 - 通過
document.documentElement.scrollTop
獲取瀏覽器視窗頂部與文件頂部之間的距離,也就是滾動條滾動的距離
然後判斷②-③
方法二(推薦)
通過getBoundingClientRect()
方法來獲取元素的大小以及位置,MDN上是這樣描述的:
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.
這個方法返回一個名為ClientRect
的DOMRect
物件,包含了top
、right
、botton
、left
、width
、height
這些值。
MDN上有這樣一張圖:
可以看出返回的元素位置是相對於左上角而言的,而不是邊距。
我們思考一下,什麼情況下圖片進入可視區域。
假設const bound = el.getBoundingClientRect();
來表示圖片到可視區域頂部距離;
並設 const clientHeight = window.innerHeight;
來表示可視區域的高度。
隨著滾動條的向下滾動,bound.top
會越來越小,也就是圖片到可視區域頂部的距離越來越小,當bound.top===clientHeight
時,圖片的上沿應該是位於可視區域下沿的位置的臨界點,再滾動一點點,圖片就會進入可視區域。
也就是說,在bound.top時,圖片是在可視區域內的。
我們這樣判斷:
1 2 3 4 5 6 7 |
function isInSight(el) { const bound = el.getBoundingClientRect(); const clientHeight = window.innerHeight; //如果只考慮向下滾動載入 //const clientWidth = window.innerWeight; return bound.top <= clientHeight + 100; } |
這裡有個+100是為了提前載入。
經提醒。。這個方法效能
載入圖片
頁面開啟時需要對所有圖片進行檢查,是否在可視區域內,如果是就載入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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; } } |
這裡應該是有一個優化的地方,設一個識別符號標識已經載入圖片的index,當滾動條滾動時就不需要遍歷所有的圖片,只需要遍歷未載入的圖片即可。
函式節流
在類似於滾動條滾動等頻繁的DOM操作時,總會提到“函式節流、函式去抖”。
所謂的函式節流,也就是讓一個函式不要執行的太頻繁,減少一些過快的呼叫來節流。
基本步驟:
- 獲取第一次觸發事件的時間戳
- 獲取第二次觸發事件的時間戳
- 時間差如果大於某個閾值就執行事件,然後重置第一個時間
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
就是呼叫函式的時間間隔,無論多麼頻繁的呼叫fn
,只有remaining>=mustRun
時fn
才能被執行。
實驗
頁面開啟時
可以看出此時僅僅是載入了img1和img2,其它的img都沒傳送請求,看看此時的瀏覽器
第一張圖片是完整的呈現了,第二張圖片剛進入可視區域,後面的就看不到了~
頁面滾動時
當我向下滾動,此時瀏覽器是這樣
此時第二張圖片完全顯示了,而第三張圖片顯示了一點點,這時候我們看看請求情況
img3的請求發出來,而後面的請求還是沒發出~
全部載入時
當滾動條滾到最底下時,全部請求都應該是發出的,如圖
完整demo
在這哦:http://axuebin.com/lazyload
原文地址:http://axuebin.com/blog/2017/…
更新
方法三 IntersectionObserver
經大佬提醒,發現了這個方法
先附上鍊接:
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
可以自動觀察元素是否在視口內。
1 2 3 4 5 6 7 |
var io = new IntersectionObserver(callback, option); // 開始觀察 io.observe(document.getElementById('example')); // 停止觀察 io.unobserve(element); // 關閉觀察器 io.disconnect(); |
callback的引數是一個陣列,每個陣列都是一個IntersectionObserverEntry
物件,包括以下屬性:
屬性 | 描述 |
---|---|
time | 可見性發生變化的時間,單位為毫秒 |
rootBounds | 與getBoundingClientRect()方法的返回值一樣 |
boundingClientRect | 目標元素的矩形區域的資訊 |
intersectionRect | 目標元素與視口(或根元素)的交叉區域的資訊 |
intersectionRatio | 目標元素的可見比例,即intersectionRect佔boundingClientRect的比例,完全可見時為1,完全不可見時小於等於0 |
target | 被觀察的目標元素,是一個 DOM 節點物件 |
我們需要用到intersectionRatio
來判斷是否在可視區域內,當intersectionRatio > 0 && intersectionRatio 即在可視區域內。
程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 io.unobserve(el); }); }); |