天天看一片代碼系列(四):layzr.js,處理圖片懶加載的庫

所謂圖片的懶加載,即只有當圖片處於或者接近於當前視窗時纔開始加載圖片。該庫的使用方法很是簡單:node

var layzr = new Layzr({ 
  attr: 'data-layzr',   // attr和retinaAttr必須至少有一個,用於指定對應的圖片
  retinaAttr: 'data-layzr-retina', // 通常對應的圖像比attr要高清
  threshold: 0,  // 距離視窗的距離爲多少時開始加載
  callback: null  // 回調函數
});

代碼解析

首先是包裝成爲umd的方式:瀏覽器

(function(root, factory) {
  if(typeof define === 'function' && define.amd) {
    define([], factory); // 使用amd定義一個模塊,依賴爲空
  } else if(typeof exports === 'object') {
    module.exports = factory(); // cmd方式,暴露返回值
  } else {
    root.Layzr = factory(); // 瀏覽器環境下
  }
}(this, function() {


})

接下來是構造函數:函數

  function Layzr( options ) {
    this._lastScroll = 0;
    this._ticking = false;
 
    // 參數
    this._optionsAttr = options.attr || 'data-layzr';
    this._optionsAttrRetina = options.retinaAttr || 'data-layzr-retina';
    this._optionsThreshold = options.threshold || 0;
    this._optionsCallback = options.callback || null;
 
    // 獲取合適的屬性
    this._retina = window.devicePixelRatio > 1;
    this._imgAttr = this._retina ? this._optionsAttrRetina : this._optionsAttr;
 
    // 全部的圖像集合
    this._images = document.getElementsByTagName('img');
 
    // call to create
    this._create();
  }

create和destory函數:優化

Layzr.prototype._create = function() {
    // 記錄初始的scroll位置
    this._requestScroll();
 
    // scroll和resize對應的事件處理函數
    window.addEventListener('scroll', this._requestScroll.bind(this), false);
    window.addEventListener('resize', this._requestScroll.bind(this), false);
  }
 
  Layzr.prototype._destroy = function() {
    // unbind事件
    window.removeEventListener('scroll', this._requestScroll.bind(this), false);
    window.removeEventListener('resize', this._requestScroll.bind(this), false);
  }

requestScroll的具體實現:this

  Layzr.prototype._requestScroll = function() {
    this._lastScroll = window.scrollY || window.pageYOffset; // 垂直方向上的滾動距離
    this._requestTick();
  }
 
  Layzr.prototype._requestTick = function() {
    if(!this._ticking) {
     // requestAnimationFrame主要用於繪製圖像,經過優化提升效率
     // 這裏用於每次滾動都調用update
      requestAnimationFrame(this.update.bind(this));
      this._ticking = true;
    }
  }

  Layzr.prototype.update = function() {
    var imagesLength = this._images.length;
    for(var i = 0; i < imagesLength; i++) {
      var image = this._images[i];
 
     // 若是當前的圖片有設定的屬性
      if(image.hasAttribute(this._imgAttr) || image.hasAttribute(this._optionsAttr)) {
        // 且已經處於視窗中
        if(this._inViewport(image)) {
          // 加載這個圖片
          this.reveal(image);
        }
      }
    }
 
    // allow for more animation frames
    this._ticking = false;
  }

是否在視窗中的判斷:spa

  Layzr.prototype._inViewport = function( imageNode ) {
    // 視窗的頂部和底部
    var viewportTop = this._lastScroll;
    var viewportBottom = viewportTop + window.innerHeight;
 
    // 圖像的頂部和底部
    var elementTop = this._getOffset(imageNode);
    var elementBottom = elementTop + imageNode.offsetHeight;
 
    // 計算threshold對應的像素
    var threshold = (this._optionsThreshold / 100) * window.innerHeight;
 
    // 是否在這個區間中
    return elementBottom >= viewportTop - threshold && elementBottom <= viewportBottom + threshold;
  }

展現圖像的實現:prototype

Layzr.prototype.reveal = function( imageNode ) {
    // 獲取圖像的src
    var source = imageNode.getAttribute(this._imgAttr) || imageNode.getAttribute(this._optionsAttr);
 
    // 去除設置的屬性
    imageNode.removeAttribute(this._optionsAttr);
    imageNode.removeAttribute(this._optionsAttrRetina);
 
    //設置src
    if(source) {
      imageNode.setAttribute('src', source);
 
      // 調用callback
      if(typeof this._optionsCallback === 'function') {
        this._optionsCallback.call(imageNode);
      }
    }
  }

總結

  1. 基本流程: 滾動--》記錄位置--》遍歷圖片--》判斷是否在視窗中--》從屬性中獲取並設置圖像src--》調用回調函數
  2. window.scrollY || window.pageYOffset 用於獲取垂直滾動的距離
  3. 視窗高度:window.innerHeight,元素高度: node.offsetHeight
  4. 獲取元素相對於doucment頂部的距離:http://stackoverflow.com/questions/5598743/finding-elements-position-relative-to-the-document
相關文章
相關標籤/搜索