成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

圖片懶加載的前世今生

zhaot / 2199人閱讀

摘要:配置項配置項中的參數(shù)有以下三個所監(jiān)聽對象的具體祖先元素,默認(rèn)是計算交叉狀態(tài)時,將附加到祖先元素上,從而有效的擴大或者縮小祖先元素判定區(qū)域設(shè)置一系列的閾值,當(dāng)交叉狀態(tài)達(dá)到閾值時,會觸發(fā)回調(diào)函數(shù)。

一、前言

??通常情況下,HTML 中的圖片資源會自上而下依次加載,而部分圖片只有在用戶向下滾動頁面的場景下才能被看見,否則這部分圖片的流量就白白浪費了。

??所以,對于那些含有大量圖片資源的網(wǎng)站,會采用“按需加載”的方式,也就是當(dāng)圖片資源出現(xiàn)在視口區(qū)域內(nèi),才會被加載,這樣可能會影響一丟丟用戶體驗,但是能大大節(jié)省網(wǎng)站的流量。

??而上述“按需加載”的方式就是今天的主角 -- 圖片懶加載技術(shù)

二、原理

??圖片懶加載技術(shù)主要通過監(jiān)聽圖片資源容器是否出現(xiàn)在視口區(qū)域內(nèi),來決定圖片資源是否被加載。

??那么實現(xiàn)圖片懶加載技術(shù)的核心就是如何判斷元素處于視口區(qū)域之內(nèi)。

三、前世

??早前實現(xiàn)的思路:

給目標(biāo)元素指定一張占位圖,將真實的圖片鏈接存儲在自定義屬性中,通常是data-src;

監(jiān)聽與用戶滾動行為相關(guān)的 scroll 事件;

在 scroll 事件處理程序中利用 Element.getBoundingClientRect() 方法判斷目標(biāo)元素與視口的交叉狀態(tài);

當(dāng)目標(biāo)元素與視口的交叉狀態(tài)大于0時,將真實的圖片鏈接賦給目標(biāo)元素 src 屬性或者 backgroundImage 屬性。

1、scroll 事件

??scroll 事件可能會被高頻度的觸發(fā),而按照上述思路,必然會在 scroll 事件處理程序中出現(xiàn)大量的 DOM 操作,這很可能會使頁面不再“如絲般順滑”,這時就需要降低 DOM 操作的頻率。

??降低 DOM 操作的頻率可以采用函數(shù)節(jié)流的方式,函數(shù)節(jié)流能夠確保在固定時間間隔只執(zhí)行一次操作。

??與函數(shù)節(jié)流相關(guān)的另一個概念叫做函數(shù)防抖,這位兄弟同樣等待一個時間間隔執(zhí)行操作,但是它被打斷之后就需要重新開始計時。

??這也是此場景下采用函數(shù)節(jié)流的原因。在 JavaScript 中可以采用 setTimeout + 閉包的方式實現(xiàn)函數(shù)節(jié)流:

function throttle (fn, interval = 500) {
  let timer = null
  let firstTime = true

  return function (...args) {
    if (firstTime) {
      // 第一次加載
      fn.apply(this, args)
      return firstTime = false
    }

    if (timer) {
      // 定時器正在執(zhí)行中,跳過
      return
    }

    timer = setTimeout(() => {
      clearTimeout(timer)
      timer = null
      fn.apply(this, args)
    }, interval)

  }
}

??除了上述 setTimeout + 閉包的實現(xiàn)方式,還可以通過 window.requestAnimationFrame() 方法實現(xiàn),這里不再贅述,有興趣的同學(xué)可以自己嘗試嘗試。

2、getBoundingClientRect()方法

??JavaScript 提供 Element.getBoundingClientRect() 方法返回元素的大小以及相對于視口的位置信息,接下來會用到返回對象的四個屬性:

top 和 left 是目標(biāo)元素左上角坐標(biāo)與網(wǎng)頁左上角坐標(biāo)的偏移值;

width 和 height 是目標(biāo)元素自身的寬度和高度。

??再結(jié)合視口的高度和寬度,即可判斷元素是否出現(xiàn)在視口區(qū)域內(nèi):

function isElementInViewport (el) {
  const { top, height, left, width } = el.getBoundingClientRect()
  const w = window.innerWidth || document.documentElement.clientWidth
  const h = window.innerHeight || document.documentElement.clientHeight
  return (
    top <= h &&
    (top + height) >= 0 &&
    left <= w &&
    (left + width) >= 0
  )
}
3、實現(xiàn)

??接下來在實現(xiàn)圖片懶加載的過程中,還需要注意一些小問題:

scroll 事件只有在滾動行為發(fā)生時,才會被觸發(fā),這里需要手動加載一次首屏的圖片;

利用 addEventListener 注冊事件處理程序時,需要保存事件處理程序的引用,以便銷毀注冊的事件程序。

function LazyLoad (el, options) {
  if (!(this instanceof LazyLoad)) {
    return new LazyLoad(el)
  }

  this.setting = Object.assign({}, { src: "data-src", srcset: "data-srcset", selector: ".lazyload" }, options)

  if (typeof el === "string") {
    el = document.querySelectorAll(el)
  }
  this.images = Array.from(el)

  this.listener = this.loadImage()
  this.listener()
  this.initEvent()
}

LazyLoad.prototype = {
  loadImage () {
    return throttle(function () {
      let startIndex = 0
      while (startIndex < this.images.length) {
        const image = this.images[startIndex]
        if (isElementInViewport(image)) {
          const src = image.getAttribute(this.setting.src)
          const srcset = image.getAttribute(this.setting.srcset)
          if (image.tagName.toLowerCase() === "img") {
            if (src) {
              image.src = src
            }
            if (srcset) {
              image.srcset = srcset
            }
          } else {
            image.style.backgroundImage = `url(${src})`
          }
          this.images.splice(startIndex, 1)
          continue
        }
        startIndex++
      }
      
      if (!this.images.length) {
        this.destroy()
      }
    }).bind(this)
  },
  initEvent () {
    window.addEventListener("scroll", this.listener, false)
  },
  destroy () {
    window.removeEventListener("scroll", this.listener, false)
    this.images = null
    this.listener = null
  }
}
四、今生

??如今,Web為開發(fā)者提供了 IntersectionObserver 接口,它可以異步監(jiān)聽目標(biāo)元素與其祖先或視窗的交叉狀態(tài),注意這個接口是異步的,它不隨著目標(biāo)元素的滾動同步觸發(fā),所以它并不會影響頁面的滾動性能。

??IntersectionObserver 構(gòu)造函數(shù)接收兩個參數(shù),回調(diào)函數(shù)以及配置項。

1、配置項

??配置項中的參數(shù)有以下三個:

root:所監(jiān)聽對象的具體祖先元素,默認(rèn)是 viewport ;

rootMargin:計算交叉狀態(tài)時,將 margin 附加到祖先元素上,從而有效的擴大或者縮小祖先元素判定區(qū)域;

threshold:設(shè)置一系列的閾值,當(dāng)交叉狀態(tài)達(dá)到閾值時,會觸發(fā)回調(diào)函數(shù)。

2、回調(diào)函數(shù)

??IntersectionObserver 實例執(zhí)行回調(diào)函數(shù)時,會傳遞一個包含 IntersectionObserverEntry 對象的數(shù)組,該對象一共有七大屬性:

time:返回一個記錄從 IntersectionObserver 的時間原點到交叉被觸發(fā)的時間的時間戳;

target:目標(biāo)元素;

rootBounds:祖先元素的矩形區(qū)域信息;

boundingClientRect:目標(biāo)元素的矩形區(qū)域信息,與前面提到的 Element.getBoundingClientRect() 方法效果一致;

intersectionRect:祖先元素與目標(biāo)元素相交區(qū)域信息;

intersectionRatio:返回intersectionRect 與 boundingClientRect 的比例值;

isIntersecting:目標(biāo)元素是否與祖先元素相交。

3、實現(xiàn)

??在此之前,還需要了解 IntersectionObserver 實例方法:

observe:開始監(jiān)聽一個目標(biāo)元素;

unobserve:停止監(jiān)聽特定的元素;

disconnect:使 IntersectionObserver 對象停止監(jiān)聽工作;

takeRecords:為所有監(jiān)聽目標(biāo)返回一個 IntersectionObserverEntry 對象數(shù)組并且停止監(jiān)聽這些目標(biāo)。

??以下為實現(xiàn)代碼:

function LazyLoad (images, options = {}) {
  if (!(this instanceof LazyLoad)) {
    return new LazyLoad(images, options)
  }
  this.setting = Object.assign({}, { src: "data-src", srcset: "data-srcset", selector: ".lazyload" }, options)
  this.images = images || document.querySelectorAll(this.setting.selector)
  this.observer = null
  this.init()
}

LazyLoad.prototype.init = function () {
  let self = this
  let observerConfig = {
    root: null,
    rootMargin: "0px",
    threshold: [0]
  }
  this.observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      const target = entry.target
      if (entry.intersectionRatio > 0) {
        this.observer.unobserve(target)
        const src = target.getAttribute(this.setting.src)
        const srcset = target.getAttribute(this.setting.srcset)
        if ("img" === target.tagName.toLowerCase()) {
          if (src) {
            target.src = src
          }
          if (srcset) {
            target.srcset = srcset
          }
        } else {
          target.style.backgroundImage = `url(${src})`
        }
      }
    })
  }, observerConfig)

  this.images.forEach(image => this.observer.observe(image))
}
五、總結(jié)

??到此,實現(xiàn)圖片懶加載主要有兩種方法:

監(jiān)聽 scroll 事件,通過 getBoundingClientRect() 計算目標(biāo)元素與視口的交叉狀態(tài);

IntersectionObserver 接口。

??第二種方法更加省心,到目前為止兼容性也還行:

??相比較下,第一種方法需要從各個方面去優(yōu)化 scroll 事件,從而達(dá)到頁面滾動“如絲般順滑”的效果。

??最后,放上兩篇關(guān)于滾動優(yōu)化的文章。

百度外賣前端 -- 如何不擇手段提升scroll事件的性能

高性能滾動 scroll 及頁面渲染優(yōu)化

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/102799.html

相關(guān)文章

  • HTTP 前世今生:一次性搞懂 HTTP、HTTPS、SPDY、HTTP2

    摘要:的最早版本誕生在年,這個最早版本和現(xiàn)在比起來極其簡單,沒有頭,沒有狀態(tài)碼,甚至版本號也沒有,后來它的版本號才被定為來和其他版本的區(qū)分。由于沒有狀態(tài)碼和錯誤代碼,如果服務(wù)器處理的時候發(fā)生錯誤,只會傳回一個特殊的包含問題描述信息的文件。 作為互聯(lián)網(wǎng)通信協(xié)議的一員老將,HTTP 協(xié)議走到今天已經(jīng)經(jīng)歷了三次版本的變動,現(xiàn)在最新的版本是 HTTP2.0,相信大家早已耳熟能詳。今天就給大家好好介紹...

    avwu 評論0 收藏0
  • Web技術(shù)前世今生(二)

    摘要:前言我是,如果你還不認(rèn)識我,不妨先看看技術(shù)的前世今生一平靜的生活已經(jīng)有一段日子了。傳送門技術(shù)的前世今生一技術(shù)的前世今生三 前言:我是JavaScript,如果你還不認(rèn)識我,不妨先看看《Web技術(shù)的前世今生(一)》 平靜的生活已經(jīng)有一段日子了。 這一天,HTML大哥面露不悅地走過來問我: Js,你是打算和我們分家嗎? 大哥,您這說的哪里話,我什么地方做的不對么?我一臉茫然地回答道。 哼,...

    Stardustsky 評論0 收藏0
  • Web技術(shù)前世今生(二)

    摘要:前言我是,如果你還不認(rèn)識我,不妨先看看技術(shù)的前世今生一平靜的生活已經(jīng)有一段日子了。傳送門技術(shù)的前世今生一技術(shù)的前世今生三 前言:我是JavaScript,如果你還不認(rèn)識我,不妨先看看《Web技術(shù)的前世今生(一)》 平靜的生活已經(jīng)有一段日子了。 這一天,HTML大哥面露不悅地走過來問我: Js,你是打算和我們分家嗎? 大哥,您這說的哪里話,我什么地方做的不對么?我一臉茫然地回答道。 哼,...

    hyuan 評論0 收藏0

發(fā)表評論

0條評論

zhaot

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<