本文在github作了收錄 github.com/Michael-lzg…html
demo源碼地址 github.com/Michael-lzg…vue
在類電商類項目,每每存在大量的圖片,如 banner 廣告圖,菜單導航圖,美團等商家列表頭圖等。圖片衆多以及圖片體積過大每每會影響頁面加載速度,形成不良的用戶體驗,因此進行圖片懶加載優化勢在必行。webpack
咱們先來看一下頁面啓動時加載的圖片信息。git
如圖所示,這個頁面啓動時加載了幾十張圖片(甚至更多),而這些圖片請求幾乎是併發的,在 Chrome 瀏覽器,最多支持的併發請求次數是有限的,其餘的請求會推入到隊列中等待或者停滯不前,直到上輪請求完成後新的請求才會發出。因此至關一部分圖片資源請求是須要排隊等待時間的。github
在上面能夠看出,有部分圖片達到幾百 kB,設置 2M(這鍋必須運營背,非得上傳高清大圖不可?),直接致使了加載時間過長。web
針對以上狀況,進行圖片懶加載有如下優勢:vue-cli
圖片懶加載的原理主要是判斷當前圖片是否到了可視區域這一核心邏輯實現的npm
scroll
事件,對其進行事件監聽。咱們先來看下頁面結構數組
<html lang="en"> <head> <meta charset="UTF-8" /> <title>Lazyload</title> <style> img { display: block; margin-bottom: 50px; height: 200px; width: 400px; } </style> </head> <body> <img src="./img/default.png" data-src="./img/1.jpg" /> <img src="./img/default.png" data-src="./img/2.jpg" /> <img src="./img/default.png" data-src="./img/3.jpg" /> <img src="./img/default.png" data-src="./img/4.jpg" /> <img src="./img/default.png" data-src="./img/5.jpg" /> <img src="./img/default.png" data-src="./img/6.jpg" /> <img src="./img/default.png" data-src="./img/7.jpg" /> <img src="./img/default.png" data-src="./img/8.jpg" /> <img src="./img/default.png" data-src="./img/9.jpg" /> <img src="./img/default.png" data-src="./img/10.jpg" /> </body> </html>
先獲取全部圖片的 dom,經過 document.body.clientHeight
獲取可視區高度,再使用 element.getBoundingClientRect()
API 直接獲得元素相對瀏覽的 top 值, 遍歷每一個圖片判斷當前圖片是否到了可視區範圍內。代碼以下:瀏覽器
function lazyload() { let viewHeight = document.body.clientHeight //獲取可視區高度 let imgs = document.querySelectorAll('img[data-src]') imgs.forEach((item, index) => { if (item.dataset.src === '') return // 用於得到頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置 let rect = item.getBoundingClientRect() if (rect.bottom >= 0 && rect.top < viewHeight) { item.src = item.dataset.src item.removeAttribute('data-src') } }) }
最後給 window 綁定 onscroll
事件
window.addEventListener('scroll', lazyload)
主要就完成了一個圖片懶加載的操做了。可是這樣存在較大的性能問題,由於 scroll
事件會在很短的時間內觸發不少次,嚴重影響頁面性能,爲了提升網頁性能,咱們須要一個節流函數來控制函數的屢次觸發,在一段時間內(如 200ms)只執行一次回調。
下面實現一個節流函數
function throttle(fn, delay) { let timer let prevTime return function (...args) { const currTime = Date.now() const context = this if (!prevTime) prevTime = currTime clearTimeout(timer) if (currTime - prevTime > delay) { prevTime = currTime fn.apply(context, args) clearTimeout(timer) return } timer = setTimeout(function () { prevTime = Date.now() timer = null fn.apply(context, args) }, delay) } }
而後修改一下 srcoll
事件
window.addEventListener('scroll', throttle(lazyload, 200))
經過上面例子的實現,咱們要實現懶加載都須要去監聽 scroll
事件,儘管咱們能夠經過函數節流的方式來阻止高頻率的執行函數,可是咱們仍是須要去計算 scrollTop
,offsetHeight
等屬性,有沒有簡單的不須要計算這些屬性的方式呢,答案就是 IntersectionObserver
。
IntersectionObserver
是一個新的 API,能夠自動"觀察"元素是否可見,Chrome 51+ 已經支持。因爲可見(visible)的本質是,目標元素與視口產生一個交叉區,因此這個 API 叫作"交叉觀察器"。咱們來看一下它的用法:
var io = new IntersectionObserver(callback, option) // 開始觀察 io.observe(document.getElementById('example')) // 中止觀察 io.unobserve(element) // 關閉觀察器 io.disconnect()
IntersectionObserver
是瀏覽器原生提供的構造函數,接受兩個參數:callback 是可見性變化時的回調函數,option 是配置對象(該參數可選)。
目標元素的可見性變化時,就會調用觀察器的回調函數 callback。callback 通常會觸發兩次。一次是目標元素剛剛進入視口(開始可見),另外一次是徹底離開視口(開始不可見)。
var io = new IntersectionObserver((entries) => { console.log(entries) })
callback 函數的參數(entries)
是一個數組,每一個成員都是一個 IntersectionObserverEntry
對象。舉例來講,若是同時有兩個被觀察的對象的可見性發生變化,entries
數組就會有兩個成員。
getBoundingClientRect()
方法的返回值,若是沒有根元素(即直接相對於視口滾動),則返回 nullintersectionRect
佔 boundingClientRect
的比例,徹底可見時爲 1,徹底不可見時小於等於 0下面咱們用 IntersectionObserver
實現圖片懶加載
const imgs = document.querySelectorAll('img[data-src]') const config = { rootMargin: '0px', threshold: 0, } let observer = new IntersectionObserver((entries, self) => { entries.forEach((entry) => { if (entry.isIntersecting) { let img = entry.target let src = img.dataset.src if (src) { img.src = src img.removeAttribute('data-src') } // 解除觀察 self.unobserve(entry.target) } }) }, config) imgs.forEach((image) => { observer.observe(image) })
Vue 中除了平時經常使用的 v-show
、v-bind
、v-for
等指令外,還能夠自定義指令。Vue 指令定義函數提供了幾個鉤子函數(可選):
實現一個懶加載指令的思路
IntersectionObserver
API,若是支持就使用 IntersectionObserver
實現懶加載,不然則使用 srcoll
事件監聽 + 節流的方法實現。Vue.directive
註冊一個 v-lazy
的指令,暴露一個 install()
函數,供 Vue 調用。main.js
裏 use(指令) 便可調用。<img>
標籤的 src
換成 v-lazy
便可實現圖片懶加載。代碼以下
新建 LazyLoad.js
文件
const LazyLoad = { // install方法 install(Vue, options) { const defaultSrc = options.default Vue.directive('lazy', { bind(el, binding) { LazyLoad.init(el, binding.value, defaultSrc) }, inserted(el) { if (IntersectionObserver) { LazyLoad.observe(el) } else { LazyLoad.listenerScroll(el) } }, }) }, // 初始化 init(el, val, def) { el.setAttribute('data-src', val) el.setAttribute('src', def) }, // 利用IntersectionObserver監聽el observe(el) { var io = new IntersectionObserver((entries) => { const realSrc = el.dataset.src if (entries[0].isIntersecting) { if (realSrc) { el.src = realSrc el.removeAttribute('data-src') } } }) io.observe(el) }, // 監聽scroll事件 listenerScroll(el) { const handler = LazyLoad.throttle(LazyLoad.load, 300) LazyLoad.load(el) window.addEventListener('scroll', () => { handler(el) }) }, // 加載真實圖片 load(el) { const windowHeight = document.documentElement.clientHeight const elTop = el.getBoundingClientRect().top const elBtm = el.getBoundingClientRect().bottom const realSrc = el.dataset.src if (elTop - windowHeight < 0 && elBtm > 0) { if (realSrc) { el.src = realSrc el.removeAttribute('data-src') } } }, // 節流 throttle(fn, delay) { let timer let prevTime return function (...args) { const currTime = Date.now() const context = this if (!prevTime) prevTime = currTime clearTimeout(timer) if (currTime - prevTime > delay) { prevTime = currTime fn.apply(context, args) clearTimeout(timer) return } timer = setTimeout(function () { prevTime = Date.now() timer = null fn.apply(context, args) }, delay) } }, } export default LazyLoad
在 main.js
裏 use 指令
import LazyLoad from './LazyLoad.js' Vue.use(LazyLoad, { default: 'xxx.png', })
將組件內 <img>
標籤的 src
換成 v-lazy
<img v-lazy="xxx.jpg" />
這樣就能完成一個 vue 懶加載的指令了。
w你必須知道的webpack插件原理分析
webpack的異步加載原理及分包策略
總結18個webpack插件,總會有你想要的!
搭建一個 vue-cli4+webpack 移動端框架(開箱即用)
從零構建到優化一個相似vue-cli的腳手架
封裝一個toast和dialog組件併發布到npm
從零開始構建一個webpack項目
總結幾個webpack打包優化的方法
總結vue知識體系之高級應用篇
總結vue知識體系之實用技巧
總結vue知識體系之基礎入門篇
總結移動端H5開發經常使用技巧(乾貨滿滿哦!)