每天看一片代码系列(四):layzr.js,处理图片懒加载的库

所谓图片的懒加载,即只有当图片处于或者接近于当前视窗时才开始加载图片。该库的使用方法非常简单:

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的具体实现:

  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;
  }

是否在视窗中的判断:

  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;
  }

展示图像的实现:

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
posted @ 2015-04-20 23:07  cubika  阅读(954)  评论(0编辑  收藏  举报