前端入門之(vue圖片加載框架三)

前言: 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張圖片,然后我們看一下流量消耗:

屏幕快照 2018-08-08 下午4.44.01.png

可以看到,我們消耗了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)窗體:


屏幕快照 2018-08-08 下午5.01.26.png

所以我們需要在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í)候:

屏幕快照 2018-08-08 下午6.16.08.png

我們可以看到.回調(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()
      })
     ...
    }

我們跑一下代碼看一下效果:

2.gif

我們可以看到log,當(dāng)我們滑動(dòng)的時(shí)候當(dāng)快滑動(dòng)到某個(gè)img的時(shí)候,我們才去加載當(dāng)前img,我們對(duì)比一下流量消耗:


屏幕快照 2018-08-08 下午6.36.14.png

可以看到,效果還是很明顯的第一屏的時(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)行代碼:


2.gif

當(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):

屏幕快照 2018-08-08 下午8.00.13.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俭嘁,一起剝皮案震驚了整個(gè)濱河市躺枕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖屯远,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔓姚,死亡現(xiàn)場(chǎng)離奇詭異捕虽,居然都是意外死亡慨丐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門泄私,熙熙樓的掌柜王于貴愁眉苦臉地迎上來房揭,“玉大人,你說我怎么就攤上這事晌端⊥北” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵咧纠,是天一觀的道長(zhǎng)蓬痒。 經(jīng)常有香客問我,道長(zhǎng)漆羔,這世上最難降的妖魔是什么梧奢? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮演痒,結(jié)果婚禮上亲轨,老公的妹妹穿的比我還像新娘。我一直安慰自己鸟顺,他們只是感情好惦蚊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讯嫂,像睡著了一般蹦锋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上欧芽,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天莉掂,我揣著相機(jī)與錄音,去河邊找鬼渐裸。 笑死巫湘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昏鹃。 我是一名探鬼主播尚氛,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼洞渤!你這毒婦竟也來了阅嘶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎讯柔,沒想到半個(gè)月后抡蛙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魂迄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年粗截,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捣炬。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡熊昌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出湿酸,到底是詐尸還是另有隱情婿屹,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布推溃,位于F島的核電站昂利,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏铁坎。R本人自食惡果不足惜蜂奸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厢呵。 院中可真熱鬧窝撵,春花似錦、人聲如沸襟铭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寒砖。三九已至赐劣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哩都,已是汗流浹背魁兼。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漠嵌,地道東北人咐汞。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像儒鹿,于是被迫代替她去往敵國(guó)和親化撕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359