試一下本身擼一個圖片懶加載...javascript
Demo地址:axuebin.com/lazyload
源碼地址:github.com/axuebin/laz…html
照片都是本身拍的哦~java
懶加載其實就是延遲加載,是一種對網頁性能優化的方式,好比當訪問一個頁面的時候,優先顯示可視區域的圖片而不一次性加載全部圖片,當須要顯示的時候再發送圖片請求,避免打開網頁時加載過多資源。git
當頁面中須要一次性載入不少圖片的時候,每每都是須要用懶加載的。github
咱們都知道HTML中的<img>
標籤是表明文檔中的一個圖像。。說了個廢話。。api
<img>
標籤有一個屬性是src
,用來表示圖像的URL,當這個屬性的值不爲空時,瀏覽器就會根據這個值發送請求。若是沒有src
屬性,就不會發送請求。數組
嗯?貌似這點能夠利用一下?瀏覽器
我先不設置src
,須要的時候再設置?性能優化
nice,就是這樣。app
咱們先不給<img>
設置src
,把圖片真正的URL放在另外一個屬性data-src
中,在須要的時候也就是圖片進入可視區域的以前,將URL取出放到src
中。
<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>複製代碼
仔細觀察一下,<img>
標籤此時是沒有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<=clientHeight
時,圖片是在可視區域內的。
咱們這樣判斷:
function isInSight(el) {
const bound = el.getBoundingClientRect();
const clientHeight = window.innerHeight;
//若是隻考慮向下滾動加載
//const clientWidth = window.innerWeight;
return bound.top <= clientHeight + 100;
}複製代碼
這裏有個+100是爲了提早加載。
頁面打開時須要對全部圖片進行檢查,是否在可視區域內,若是是就加載。
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操做時,總會提到「函數節流、函數去抖」。
所謂的函數節流,也就是讓一個函數不要執行的太頻繁,減小一些過快的調用來節流。
基本步驟:
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 = 0;
}
const remaining = now - previous;
if (mustRun && remaining >= mustRun) {
fn.apply(context, args);
previous = now;
}
}
}複製代碼
這裏的mustRun
就是調用函數的時間間隔,不管多麼頻繁的調用fn
,只有remaining>=mustRun
時fn
才能被執行。
能夠看出此時僅僅是加載了img1和img2,其它的img都沒發送請求,看看此時的瀏覽器
第一張圖片是完整的呈現了,第二張圖片剛進入可視區域,後面的就看不到了~
當我向下滾動,此時瀏覽器是這樣
此時第二張圖片徹底顯示了,而第三張圖片顯示了一點點,這時候咱們看看請求狀況
img3的請求發出來,然後面的請求仍是沒發出~
當滾動條滾到最底下時,所有請求都應該是發出的,如圖
經大佬提醒,發現了這個方法
先附上連接:
jjc大大:github.com/justjavac/t…
阮一峯大大:www.ruanyifeng.com/blog/2016/1…
API Sketch for Intersection Observers:github.com/WICG/Inters…
IntersectionObserver
能夠自動觀察元素是否在視口內。
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
即在可視區域內。
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);
});
});複製代碼