vue 图片拖动加载 类似于地图_前端性能优化之图片懒加载(附vue自定义指令)...
作者:lzg9527
鏈接:https://juejin.cn/post/6903774214780616718
在類電商類項目,往往存在大量的圖片,如 banner 廣告圖,菜單導航圖,美團等商家列表頭圖等。圖片眾多以及圖片體積過大往往會影響頁面加載速度,造成不良的用戶體驗,所以進行圖片懶加載優化勢在必行。
為什么要進行圖片懶加載
我們先來看一下頁面啟動時加載的圖片信息。
如圖所示,這個頁面啟動時加載了幾十張圖片(甚至更多),而這些圖片請求幾乎是并發的,在 Chrome 瀏覽器,最多支持的并發請求次數是有限的,其他的請求會推入到隊列中等待或者停滯不前,直到上輪請求完成后新的請求才會發出。所以相當一部分圖片資源請求是需要排隊等待時間的。
在上面可以看出,有部分圖片達到幾百 kB,甚至 2M(這鍋必須運營背,非得上傳高清大圖不可?),直接導致了加載時間過長。
針對以上情況,進行圖片懶加載有以下優點:
- 減少資源的加載,頁面啟動只加載首屏的圖片,這樣能明顯減少了服務器的壓力和流量,也能夠減小瀏覽器的負擔。
- 防止并發加載的資源過多而阻塞 js 的加載,影響整個網站的啟動。
- 能提升用戶的體驗,不妨設想下,用戶打開頁面的時候,如果頁面上所有的圖片都需要加載,由于圖片數目較大,等待時間很長這就嚴重影響用戶體驗。
圖片懶加載的原理
圖片懶加載的原理主要是判斷當前圖片是否到了可視區域這一核心邏輯實現的
- 拿到所有的圖片 dome 。
- 遍歷每個圖片判斷當前圖片是否到了可視區范圍內。
- 如果到了就設置圖片的 src 屬性。
- 綁定 window 的 scroll 事件,對其進行事件監聽。
我們先來看下頁面結構
"en">??
????"UTF-8"?/>
????Lazyload
????
??
??
????"./img/default.png"?src="./img/1.jpg"?/>
????"./img/default.png"?src="./img/2.jpg"?/>
????"./img/default.png"?src="./img/3.jpg"?/>
????"./img/default.png"?src="./img/4.jpg"?/>
????"./img/default.png"?src="./img/5.jpg"?/>
????"./img/default.png"?src="./img/6.jpg"?/>
????"./img/default.png"?src="./img/7.jpg"?/>
????"./img/default.png"?src="./img/8.jpg"?/>
????"./img/default.png"?src="./img/9.jpg"?/>
????"./img/default.png"?src="./img/10.jpg"?/>
??
先獲取所有圖片的 dom,通過 document.body.clientHeight 獲取可視區高度,再使用 element.getBoundingClientRect()API 直接得到元素相對瀏覽的 top 值, 遍歷每個圖片判斷當前圖片是否到了可視區范圍內。代碼如下:
function?lazyload()?{??let?viewHeight?=?document.body.clientHeight?//獲取可視區高度
??let?imgs?=?document.querySelectorAll('img[src]')
??imgs.forEach((item,?index)?=>?{
????if?(item.dataset.src?===?'')?return
????//?用于獲得頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置
????let?rect?=?item.getBoundingClientRect()
????if?(rect.bottom?>=?0?&&?rect.top???????item.src?=?item.dataset.src
??????item.removeAttribute('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))IntersectionObserver
通過上面例子的實現,我們要實現懶加載都需要去監聽 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 數組就會有兩個成員。
- time:可見性發生變化的時間,是一個高精度時間戳,單位為毫秒
- target:被觀察的目標元素,是一個 DOM 節點對象
- isIntersecting: 目標是否可見
- rootBounds:根元素的矩形區域的信息,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對于視口滾動),則返回 null
- boundingClientRect:目標元素的矩形區域的信息
- intersectionRect:目標元素與視口(或根元素)的交叉區域的信息
- intersectionRatio:目標元素的可見比例,即 intersectionRect 占 boundingClientRect 的比例,完全可見時為 1,完全不可見時小于等于 0
下面我們用 IntersectionObserver 實現圖片懶加載
const?imgs?=?document.querySelectorAll('img[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('src')
??????}
??????//?解除觀察
??????self.unobserve(entry.target)
????}
??})
},?config)
imgs.forEach((image)?=>?{
??observer.observe(image)
})
懶加載指令
Vue 中除了平時常用的 v-show、v-bind、v-for 等指令外,還可以自定義指令。Vue 指令定義函數提供了幾個鉤子函數(可選):
- bind: 只調用一次,指令第一次綁定到元素時調用,可以定義一個在綁定時執行一次的初始化動作。
- inserted: 被綁定元素插入父節點時調用(父節點存在即可調用,不必存在于 document 中)。
- update: 被綁定元素所在的模板更新時調用,而不論綁定值是否變化。通過比較更新前后的綁定值。
- componentUpdated: 被綁定元素所在模板完成一次更新周期時調用。unbind: 只調用一次, 指令與元素解綁時調用。
實現一個懶加載指令的思路
- 判斷瀏覽器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 實現懶加載,否則則使用 srcoll 事件監聽 + 節流的方法實現。
- 通過 Vue.directive 注冊一個 v-lazy 的指令,暴露一個 install() 函數,供 Vue 調用。
- 在 main.js 里 use(指令) 即可調用。
- 將組件內 ?標簽的 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('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('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)?{
??????if?(realSrc)?{
????????el.src?=?realSrc
????????el.removeAttribute('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',
})
將組件內 ?標簽的 src 換成 v-lazy
"xxx.jpg"?/>這樣就能完成一個 vue 懶加載的指令了。
小結
- 提高網站加載性能,圖片懶加載是必要的。
- 圖片懶加載是實現原理是判斷當前圖片是否到了可視區域進行加載,可通過監聽 scroll 事件和 IntersectionObserver 實現相應的功能。
- 可通過 Vue.directive 編寫圖片懶加載指令。
demo源碼地址 https://github.com/Michael-lzg/demo-code/tree/master/lazyLoad
??愛心三連擊
1.看到這里了就點個在看支持下吧,你的「在看」是我創作的動力。
2.關注公眾號猴哥說前端,「一起玩轉前端」!
3.關注公眾號回復【加群】,拉你進技術交流群一起玩轉前端。
“在看轉發”是最大的支持總結
以上是生活随笔為你收集整理的vue 图片拖动加载 类似于地图_前端性能优化之图片懒加载(附vue自定义指令)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 过滤emoji表情符号,
- 下一篇: quartz.net隔一天执行一次_你知