圖片對頁面加載速度影響很是大前端
當頁面圖片比較多,加載速度慢,很是影響用戶體驗 vue
思考一下,頁面有可能有幾百張圖片,可是首屏上須要展現的可能就一張而已,其餘的那些圖片能不能晚一點再加載,好比用戶往下滾動的時候…… git
這是爲何要用懶加載的緣由 github
那預加載呢? 這個很是語義化,預備,提早…… 就是讓用戶感受到你加載圖片很是快,甚至用戶沒有感覺到你在加載圖片
後端
圖片先用佔位符表示,不要將圖片地址放到src
屬性中,而是放到其它屬性(data-original
)中 頁面加載完成後,監聽窗口滾動,當圖片出如今視窗中時再給它賦予真實的圖片地址,也就是將data-original
中的屬性拿出來放到src屬性中 在滾動頁面的過程當中,經過給scroll
事件綁定lazyload
函數,不斷的加載出須要的圖片
數組
注意:請對lazyload
函數使用防抖與節流,不懂這兩的能夠本身去查
bash
這種方式,本質上不算懶加載 加載完首屏內容後,隔一段時間,去加載所有內容 但這個時間差已經完成了用戶對首屏加載速度的期待
閉包
用戶點擊或者執行其餘操做再加載 其實也包括的滾動可視區域,但大部分狀況下,你們說的懶加載都是隻可視區域的圖片懶加載,因此就拿出來講了前後端分離
這裏也分爲兩種狀況:
異步
一、頁面滾動的時候計算圖片的位置與滾動的位置
二、經過新的API: IntersectionObserver API
(能夠自動"觀察"元素是否可見)Intersection Observer API - Web API 接口 | MDN
將非首屏的圖片的src屬性設置一個默認值,監聽事件scroll
、resize
、orientationchange
,判斷元素進入視口viewport時則把真實地址賦予到src
上
<img class="lazy" src="[佔位圖]" data-src="[真實url地址]" data-srcset="[不一樣屏幕密度下,不一樣的url地址]" alt="I'm an image!">
複製代碼
如上,data-*
屬於自定義屬性, ele.dataset.*
能夠讀取自定義屬性集合 img.srcset
屬性用於設置不一樣屏幕密度下,image自動加載不一樣的圖片,好比<img src="image-128.png" srcset="image-256.png 2x" />
經常使用的方式有兩種
1)、圖片距離頂部距離 < 視窗高度 + 頁面滾動高度(太LOW了~)
imgEle.offsetTop < window.innerHeight + document.body.scrollTop
複製代碼
2)getBoundingClientRect
(很舒服的一個API)
Element.getBoundingClientRect()
方法返回元素的大小及其相對於視口的位置,具體參考文檔Element.getBoundingClientRect() - Web API 接口 | MDN
function isInViewport(ele) {
// 元素頂部 距離 視口左上角 的距離top <= 窗口高度 (反例:元素在屏幕下方的狀況)
// 元素底部 距離 視口左上角 的距離bottom > 0 (反例:元素在屏幕上方的狀況)
// 元素display樣式不爲none
const notBelow = ele.getBoundingClientRect().top <= window.innerHeight ? true : false;
const notAbove = ele.getBoundingClientRect().bottom >= 0 ? true : false;
const visable = getComputedStyle(ele).display !== "none" ? true : false;
return notBelow && notAbove && visable ? true : false;
}
複製代碼
3)Intersection Observer
(存在兼容性問題,但帥啊)
因爲兼容性問題,暫時不寫,具體可參考文檔 Intersection Observer - Web API 接口 | MDN
核心內容都在上面分析完了,下面就是整合一下,
1)適合簡單的HTML文件或者服務端直出的首頁
注意DOMContentLoaded
,在DOM解析完以後立馬執行,不適合先後端分離的單頁應用,由於SPA應用通常來講圖片數據是異步請求的,在DOMContentLoaded
的時候,頁面上未必徹底解析完JS和CSS,這時候let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
拿到的不是真正首屏的全部圖片標籤
document.addEventListener("DOMContentLoaded", () => {
// 獲取全部class爲lazy的img標籤
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
// 這個active是節流throttle所用的標誌位,這裏用到了閉包知識
let active = false;
const lazyLoad = () => {
// throttle相關:200ms內只會執行一次lazyLoad方法
if (active) return;
active = true;
setTimeout(() => {
lazyImages.forEach(lazyImage => {
// 判斷元素是否進入viewport
if (isInViewport(lazyImage)) {
// <img class="lazy" src="[佔位圖]" data-src="[真實url地址]" data-srcset="[不一樣屏幕密度下,不一樣的url地址]" alt="I'm an image!">
// ele.dataset.* 能夠讀取自定義屬性集合,好比data-*
// img.srcset 屬性用於設置不一樣屏幕密度下,image自動加載不一樣的圖片 好比<img src="image-128.png" srcset="image-256.png 2x" />
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
// 刪除class 防止下次重複查找到改img標籤
lazyImage.classList.remove("lazy");
}
// 更新lazyImages數組,把還沒處理過的元素拿出來
lazyImages = lazyImages.filter(image => {
return image !== lazyImage;
});
// 當所有處理完了,移除監聽
if (lazyImages.length === 0) {
document.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationchange", lazyLoad);
}
})
active = false;
}, 200);
}
document.addEventListener("scroll", lazyLoad);
document.addEventListener("resize", lazyLoad);
document.addEventListener("orientationchange", lazyLoad);
})
複製代碼
2)、適合單頁應用的寫法(模擬封裝vue的懶加載)
① 核心實現
mounted
鉤子裏面(這樣的首屏體驗實際上是很差的),不過足夠理解就行了 et lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
的獲取時機放在了定時器裏面,不是一開始就拿到全局的lazyImages
,而是每次刷新時纔拿到還沒處理過的function LazyLoad() {
// 這個active是節流throttle所用的標誌位,這裏用到了閉包知識
let active = false;
const lazyLoad = () => {
// throttle相關:200ms內只會執行一次lazyLoad方法
if (active) return;
active = true;
setTimeout(() => {
// 獲取全部class爲lazy的img標籤,這裏因爲以前已經把處理過的img標籤的class刪掉了 因此不會重複查找
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
lazyImages.forEach(lazyImage => {
// 判斷元素是否進入viewport
if (isInViewport(lazyImage)) {
// <img class="lazy" src="[佔位圖]" data-src="[真實url地址]" data-srcset="[不一樣屏幕密度下,不一樣的url地址]" alt="I'm an image!">
// ele.dataset.* 能夠讀取自定義屬性集合,好比data-*
// img.srcset 屬性用於設置不一樣屏幕密度下,image自動加載不一樣的圖片 好比<img src="image-128.png" srcset="image-256.png 2x" />
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
// 刪除class 防止下次重複查找到改img標籤
lazyImage.classList.remove("lazy");
}
// 當所有處理完了,移除監聽
if (lazyImages.length === 0) {
document.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationchange", lazyLoad);
}
})
active = false;
}, 200);
}
document.addEventListener("scroll", lazyLoad);
document.addEventListener("resize", lazyLoad);
document.addEventListener("orientationchange", lazyLoad);
}
複製代碼
② 在全局中的`mounted`鉤子中執行
const vm = new Vue({
el: '.wrap',
store,
mounted: function () {
LazyLoad();
}
});
複製代碼
③ 封裝 img-lazy
組件
<template>
<img :class="['lazy', className]" :src="defaultImg" :data-src="url" :data-srcset="`${url} 1x`" alt="fordeal">
</template>
<script>
export default {
props: {
url: {
type: String
},
defaultImg: {
type: String,
default: [默認圖片]
className: {
type: String,
default: ''
}
}
}
</script>
複製代碼
④ 使用
<img-lazy className="image" :url="item.display_image" />複製代碼
以上實現的只是比較粗糙的版本,要真正實現性能大幅提高優化還須要處理較多的細節,本文旨在讓幫助部分同窗瞭解基本原理,有了宏觀的認識後,能夠嘗試去讀一下相關這種懶加載插件的源碼,能學到很多東西。
感謝您耐心看到這裏,但願有所收穫!
我在學習過程當中喜歡作記錄,分享的是本身在前端之路上的一些積累和思考,但願能跟你們一塊兒交流與進步,更多文章請看amandakelake的Github博客