前言: 2018已經(jīng)過了一大半了,并沒有感覺在本命年中有啥不順的,每天過得還是挺開心的??,很感謝身邊的朋友一路的陪伴,感恩!! 已經(jīng)半年多沒回家了,不久前跟麻麻發(fā)了一個(gè)視頻,跟過年比,家里妹妹長(zhǎng)了10cm,麻麻瘦了10kg,我家狗狗從小奶狗長(zhǎng)到了30kg了,麻麻說:“你胡子長(zhǎng)了要刮一刮了” ??! 才發(fā)現(xiàn)自己真的不小了~~為夢(mèng)想歷經(jīng)滄桑确丢、閱盡千帆渡冻、歸去才發(fā)現(xiàn)自己早已非少年了啊!!! 不逼逼了,進(jìn)入我們今天的主題哈~
前面已經(jīng)寫過兩篇同系列的文章了,感興趣的童鞋可以去看看哈,h5剛起步,文章純屬于個(gè)人學(xué)習(xí)筆記,大牛勿噴!
前端入門之(vue圖片加載框架一)
前端入門之(vue圖片加載框架二)
前端入門之(vue圖片加載框架二)中最后我們已經(jīng)實(shí)現(xiàn)了框架的基本功能,也就是placeholder(loading跟error)占位圖效果.
[圖片上傳失敗...(image-6288f8-1533729480712)]
最后還留了一個(gè)問題:
當(dāng)我們的圖片很多的時(shí)候,我們只需要加載我們看到的部分,當(dāng)滑動(dòng)到其它部分的時(shí)候再去加載(以時(shí)間換空間),現(xiàn)在我們是直接一出來就加載全部圖片(以空間換時(shí)間), 如果是在pc端的話,我們可以直接加載全部,這樣快,而且pc上貌似內(nèi)存問題還不是很大的問題,但是當(dāng)?shù)绞謾C(jī)端的時(shí)候,內(nèi)存的占用直接影響的是用戶體驗(yàn),所以我們需要用懶加載的方式去加載圖片
我們先看一下如果我們直接加載幾張圖片會(huì)怎么樣?
<template>
<div class="opt-container">
<img v-lazy="{src: image}" v-for="(image,index) in images" v-bind:key="'image-'+index">
</div>
</template>
<script>
const IMAGES = [
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=c136f7387cfe2b79161f2f93bff6cb96&imgtype=0&src=http%3A%2F%2Fpic1.cxtuku.com%2F00%2F09%2F65%2Fb3468db29cb1.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=de941561df3b6fd53b2df9bfd6c0b187&imgtype=0&src=http%3A%2F%2Fpic43.photophoto.cn%2F20170413%2F0008118236659168_b.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283185&di=aff7e8aa60813f6e36ebc6f6a961255c&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01d60f57e8a07d0000018c1bfa2564.JPG%403000w_1l_2o_100sh.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533726597165&di=198b9836a377021082281fcf0e5f3331&imgtype=0&src=http%3A%2F%2Fchongqing.sinaimg.cn%2Fiframe%2F159%2F2012%2F0531%2FU9278P1197T159D1F3057DT20140627094648.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533726597167&di=e06fc5f74fac9bb61d229249219cbe4f&imgtype=0&src=http%3A%2F%2Fimg2.ph.126.net%2FuWKrNCkdBNBPzdjxCcUl-w%3D%3D%2F6630220042234317764.jpg'
]
export default {
name: 'Lazy',
data() {
return {
images: IMAGES,
showImage: true
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.opt-container {
font-size: 0px;
}
</style>
可以看到,我們代碼很簡(jiǎn)單,就是直接加載了5張圖片,然后我們看一下流量消耗:
可以看到,我們消耗了1.3MB的流量,而且所有圖片都在同一時(shí)間加載的,這才5張圖片,在項(xiàng)目中,我們一個(gè)頁(yè)面幾百?gòu)垐D片也不是不可能哈,所以想想就有點(diǎn)恐怖,放到pc還好,手機(jī)上估計(jì)是要哭的節(jié)奏~ 所以我們考慮一下,當(dāng)我們滑動(dòng)的時(shí)候,我們滑動(dòng)看到的圖片才讓它加載,地下未滑到圖片我們就暫時(shí)不加載了.我們來試試哈~~
首先是監(jiān)聽窗體的滾動(dòng),因?yàn)槲覀冞@里是直接是body在滾動(dòng),所以,我們直接監(jiān)聽:
mounted(){
window.onscroll=()=>{
console.log(window.scrollY);
}
}
然后我們滑動(dòng)窗體:
所以我們需要在onscroll中通知所有的listener(經(jīng)紀(jì)人),問一下他:“我們現(xiàn)在到這個(gè)位置了,你需要不需要加載?”,我們首先得通知manager(經(jīng)理),然后由manager去通知listener(經(jīng)紀(jì)人):
首先通知LazyDelegate,那么我們?cè)趺茨玫絃azyDelegate引用呢? 我們可以在install方法執(zhí)行的時(shí)候傳給Vue的prototype,這樣每個(gè)vue的實(shí)例都會(huì)有LazyDelegate的引用了:
import lazyDelegate from './LazyDelegate';
export default {
install(Vue, options = {}) {
let LazyClass = lazyDelegate(Vue);
let lazy = new LazyClass(options);
Vue.prototype.$Lazyload = lazy
...
}
}
然后在我們demo的vue文件中:
mounted(){
window.onscroll=()=>{
this.$Lazyload.lazyLoadHandler();
}
}
然后我們經(jīng)理的lazyLoadHandler方法就會(huì)一個(gè)一個(gè)通知listener去加載圖片.
/**
* 通知所有的listener該干活了
* @private
*/
_lazyLoadHandler() {
//找出哪些是已經(jīng)完成工作了的
const freeList = []
this.ListenerQueue.forEach((listener, index) => {
if (!listener.state.error && listener.state.loaded) {
return freeList.push(listener)
}
listener.load()
})
//把完成工作的listener剔除
freeList.forEach(vm => remove(this.ListenerQueue, vm))
}
我們?cè)赺lazyLoadHandler打個(gè)log,當(dāng)我們滑動(dòng)窗體的時(shí)候:
我們可以看到.回調(diào)了我們的_lazyLoadHandler方法,進(jìn)而就會(huì)去通知所有的listener去加載圖片:
_lazyLoadHandler() {
....
this.ListenerQueue.forEach((listener, index) => {
if (!listener.state.error && listener.state.loaded) {
return freeList.push(listener)
}
listener.load()
})
...
}
前面我們說了,滑動(dòng)的時(shí)候,通知listener加載圖片還有一個(gè)條件,當(dāng)前img是否在窗體內(nèi),是否可以見?
所以我們需要加一個(gè)判斷:
_lazyLoadHandler() {
....
this.ListenerQueue.forEach((listener, index) => {
if (!listener.state.error && listener.state.loaded) {
return freeList.push(listener)
}
if(是否在窗體內(nèi),是否可以見?){
listener.load()
}
})
...
}
轉(zhuǎn)換成代碼就是:
/**
* 通知所有的listener該干活了
* @private
*/
_lazyLoadHandler() {
//找出哪些是已經(jīng)完成工作了的
console.log('_lazyLoadHandler');
const freeList = []
this.ListenerQueue.forEach((listener, index) => {
if (!listener.state.error && listener.state.loaded) {
return freeList.push(listener)
}
//判斷是否在窗體內(nèi)肮柜,不在就不去加載圖片了
if(!listener.checkInView())return;
listener.load()
})
//把完成工作的listener剔除
freeList.forEach(vm => remove(this.ListenerQueue, vm))
}
listener.js:
getRect() {
this.rect = this.el.getBoundingClientRect()
}
checkInView() {
this.getRect()
return (this.rect.top < window.innerHeight && this.rect.bottom > 0
&& this.rect.left < window.innerWidth && this.rect.right > 0)
}
我們修改一下Lazy.vue測(cè)試頁(yè)面,給img一個(gè)定高,不然默認(rèn)都加載了:
<div v-for="(image,index) in images" v-bind:key="'image-'+index">
<img v-lazy="{src: image}" width="100%" height="500px">
</div>
然后給LazyDelegate加一個(gè)log提示:
_lazyLoadHandler() {
..
//判斷是否在窗體內(nèi),不在就不去加載圖片了
if(!listener.checkInView())return;
console.log(listener.src+'可以加載了');
listener.load()
})
...
}
我們跑一下代碼看一下效果:
我們可以看到log,當(dāng)我們滑動(dòng)的時(shí)候當(dāng)快滑動(dòng)到某個(gè)img的時(shí)候,我們才去加載當(dāng)前img,我們對(duì)比一下流量消耗:
可以看到,效果還是很明顯的第一屏的時(shí)候只有739kb了,哈哈.其實(shí)也沒有啥牛逼的東西,只是換了種加載模式而已,我們之前是以空間換時(shí)間,現(xiàn)在變成了以時(shí)間換空間, 很明顯,第二種是比較符合移動(dòng)端策略的.
有童鞋會(huì)說了,你既然是框架,為啥還把滾動(dòng)的監(jiān)聽放在組件中呢? 還有,你怎么能確定別人是body在滾動(dòng)呢? 也可以是某個(gè)模塊自己在滾動(dòng)啊,這樣不就jj了? 是的!! 我們來優(yōu)化一下我們的代碼,當(dāng)我們的指令執(zhí)行到add的時(shí)候,我們創(chuàng)建監(jiān)聽者.
那么除了監(jiān)聽我們的scroll事件外我們還要監(jiān)聽哪些事件呢? 我們列一下:
const DEFAULT_EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove']
然后我們指令走add的時(shí)候獲取滾動(dòng)元素,加上監(jiān)聽:
/**
* 只調(diào)用一次抛猖,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置厚棵。
* @param el 指令所綁定的元素二汛,可以用來直接操作 DOM 。
* @param binding
* @param vnode
*/
add(el, binding, vnode) {
console.log('add');
let {src, loading, error} = this._valueFormatter(binding.value)
Vue.nextTick(() => {
const newListener = new LazyListener({
el,
loading,
error,
src,
options: this.options,
elRenderer: this._elRenderer.bind(this),
})
this.ListenerQueue.push(newListener)
//獲取滾動(dòng)元素
let $parent;
if (!$parent) {
$parent = scrollParent(el)
}
//給window添加監(jiān)聽
this._addListenerTarget(window)
//給父滾動(dòng)元素添加監(jiān)聽
this._addListenerTarget($parent)
Vue.nextTick(() => {
this.lazyLoadHandler()
})
})
}
/**
* 添加監(jiān)聽
* @param el
* @private
*/
_addListenerTarget(el) {
if (!el) return
DEFAULT_EVENTS.forEach((evt) => {
el.addEventListener(evt, this.lazyLoadHandler.bind(this), false)
})
}
function scrollParent(el) {
if (!(el instanceof HTMLElement)) {
return window
}
let parent = el
while (parent) {
if (parent === document.body || parent === document.documentElement) {
break
}
if (!parent.parentNode) {
break
}
if (/(scroll|auto)/.test(overflow(parent))) {
return parent
}
parent = parent.parentNode
}
return window
}
function overflow(el) {
return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x')
}
const style = (el, prop) => {
return typeof getComputedStyle !== 'undefined'
? getComputedStyle(el, null).getPropertyValue(prop)
: el.style[prop]
}
最后我們修改下測(cè)試頁(yè)面的代碼:
<template>
<div class="opt-container">
<div v-for="(image,index) in images" v-bind:key="'image-'+index">
<img v-lazy="{src: image}" width="100%" height="500px">
</div>
</div>
</template>
<script>
const IMAGES = [
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=c136f7387cfe2b79161f2f93bff6cb96&imgtype=0&src=http%3A%2F%2Fpic1.cxtuku.com%2F00%2F09%2F65%2Fb3468db29cb1.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=de941561df3b6fd53b2df9bfd6c0b187&imgtype=0&src=http%3A%2F%2Fpic43.photophoto.cn%2F20170413%2F0008118236659168_b.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283185&di=aff7e8aa60813f6e36ebc6f6a961255c&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01d60f57e8a07d0000018c1bfa2564.JPG%403000w_1l_2o_100sh.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533726597165&di=198b9836a377021082281fcf0e5f3331&imgtype=0&src=http%3A%2F%2Fchongqing.sinaimg.cn%2Fiframe%2F159%2F2012%2F0531%2FU9278P1197T159D1F3057DT20140627094648.jpg',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533726597167&di=e06fc5f74fac9bb61d229249219cbe4f&imgtype=0&src=http%3A%2F%2Fimg2.ph.126.net%2FuWKrNCkdBNBPzdjxCcUl-w%3D%3D%2F6630220042234317764.jpg'
]
export default {
name: 'Lazy',
data() {
return {
images: IMAGES,
showImage: true
}
},
mounted(){
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.opt-container {
font-size: 0px;
}
</style>
然后我們運(yùn)行代碼:
當(dāng)我們往上滑動(dòng)的時(shí)候,跟我們之前的效果一致了~~~
好啦!! 到這里我們圖片框架代碼都已經(jīng)解析完畢了,也帶著一起敲了一遍,有童鞋會(huì)覺得代碼有點(diǎn)熟悉哈,沒錯(cuò),就是vue-lazyload的代碼,哈哈!! 小伙伴不要把我代碼直接拖進(jìn)工程哈,要用的話直接去拖vue-lazyload的代碼,最后附上demo的github鏈接,以及vue-lazyload的github鏈接:
DEMO地址: https://github.com/913453448/VuexDemo.git
Vue-Lazyload地址:https://github.com/hilongjw/vue-lazyload
最后,歡迎志同道合的小伙伴入群,歡迎交流~~~~
qq群號(hào):