每天看一片代码系列(四):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); } } }
总结
- 基本流程: 滚动--》记录位置--》遍历图片--》判断是否在视窗中--》从属性中获取并设置图像src--》调用回调函数
- window.scrollY || window.pageYOffset 用于获取垂直滚动的距离
- 视窗高度:window.innerHeight,元素高度: node.offsetHeight
- 获取元素相对于doucment顶部的距离:http://stackoverflow.com/questions/5598743/finding-elements-position-relative-to-the-document