最近在写在手机上运行的web项目,固然要考虑到图片加载的问题,以尽可能的减少图片的加载量,在这里,就用到了图片延迟加载技术。
在github上找了一些别人写的工具拿来分析,发现其中有几个新的技术点,是值得学习借鉴的。
imagelazyloader##
maxzhang
图片延迟加载
阅读代码笔记
//闭包,默认传入为window,参数变量名又为window,可以改为其它关键字
(function(window) {
//判断是否android系统,主要为后面是否有动画的判断量
var isAndroid = /Android/i.test(window.navigator.userAgent),
//创建一个空的div对象,显拿到style属性
dummyStyle = document.createElement('div').style,
//获取css前缀类型,例如webkit,moz,ms...
//在chrome下做试验,vendor为'',则为chrome支持 没有前缀的transform
vendor = (function() {
var vendors = 't,webkitT,MozT,msT,OT'.split(','),
t,
i = 0,
l = vendors.length;
for (; i < l; i++) {
t = vendors[i] + 'ransform';
if (t in dummyStyle) {
return vendors[i].substr(0, vendors[i].length - 1);
}
}
return false;
})(),
//获取不同平台的 transitionEnd 事件名称
//,当CSS 3的animation动画执行结束时,触发webkitAnimationEnd事件,当CSS 3的transition动画执行结束时,触发webkitTransitionEnd事件
//现在最最的opare浏览器内核也是webkit的,所以 -o-应该可以不考虑了
transitionEndEvent = (function() {
if (vendor == 'webkit' || vendor === 'O') {
return vendor.toLowerCase() + 'TransitionEnd';
}
return 'transitionend';
}()),
//监听动画事件,参数分别是 节点目标,动事时间,及callback
listenTransition = function(target, duration, callbackFn) {
var me = this,
//清除事件监听
clear = function() {
//测试是否起定时,有则清除
if (target.transitionTimer) clearTimeout(target.transitionTimer);
//设置定时器为空
target.transitionTimer = null;
//移除对transitionEnd Event的监听
target.removeEventListener(transitionEndEvent, handler, false);
},
//监听方法体
handler = function() {
//首选清除监听事件
clear();
//执行callback方法
if (callbackFn) callbackFn.call(me);
};
//初始化清初事件监听
clear();
//监听target的transitionEndEvent事件,执行callback
target.addEventListener(transitionEndEvent, handler, false);
//动画时间+100ms后执行callback
target.transitionTimer = setTimeout(handler, duration + 100);
},
//代理,主要实现的是this对象的代理
proxy = function(fn, scope) {
return function() {
return fn.apply(scope, arguments);
};
};
//进入正题
//
var ImageLazyLoader = function(config) {
//设置config 的值,默认为一空对象
config = config || {};
//设置配置对象值为类属性
for (var o in config) {
this[o] = config[o];
}
//设置ct类属性值,body
this.ct = document.body;
//执行_onScroll方法,this为当前类实例
//返回一新的fn,传入的fn的this为当前类对象,以防止在下面的监听事件中,改变this
this._onScroll_ = proxy(this._onScroll, this);
//坚挺window 默认 scroll事件
window.addEventListener('scroll', this._onScroll_, false);
//设置maxScrollY默认值
this.maxScrollY = 0;
//是android的话,不使用渐出动画
if (isAndroid) { // 在android下,opacity动画效果比较差
this.useFade = false;
}
this.elements = [];
this.lazyElements = {};
this.scan(this.ct);
this._onPageShow_ = proxy(this._onPageShow, this);
//监听window事件,pageshow 事件
//第一个事件就是pageshow,这个事件在页面显示时触发,
//无论页面是否来自bfcache。在重新加载页面中,
//pageshow会在load事件触发后触发;而对于bfcache中的页面,
//pageshow会在页面状态完全恢复的那一刻触发。另外要注意的是,
//虽然这个事件的目标是document,但必须将其事件处理程序添加到window。
window.addEventListener('pageshow', this._onPageShow_, false);
};
ImageLazyLoader.prototype = {
range: 200,
//图片地址,在img标签中的属性标签
realSrcAttribute: 'data-src',
//是否使用渐变动画。默认为ture
useFade: true,
//当显示页面时执行
_onPageShow: function(e) {
//“往返缓存”(back-forward cache,或bfcache),
//可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。
//这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;
//pageshow事件的event对象还包含一个名为persisted的布尔值属性。
//如果页面中保存在了bfcache中,则这个属性的值为true;否则,这个属性的值为false。
if (e.persisted) {
//重新设置maxscroll的值
this.maxScrollY = 0;
//重新加载节点
this.scan(this.ct);
}
},
//当滚动时,判断当前值是否大于最大maxscrollY,决定是否触发scrollAc
_onScroll: function() {
var scrollY = this.getScrollY();
if (scrollY > this.maxScrollY) {
this.maxScrollY = scrollY;
this._scrollAction();
}
},
//获取当前页面的位置
getScrollY: function() {
//经测验,两个属性均可
return window.pageYOffset || window.scrollY;
},
//滚动页面时,触发,主要是将到显示区的标签装入lazyEls中
_scrollAction: function() {
//清除定时器
clearTimeout(this.lazyLoadTimeout);
this.elements = this.elements.filter(function(img) {
//getBoundingClientRect用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
//document.documentElement.clientTop; // 非IE为0,IE为2
//如果图片的位置在 将要进入可视范围内时
if ((this.range + window.innerHeight) >= (img.getBoundingClientRect().top - document.documentElement.clientTop)) {
//拿到图片地址
var realSrc = img.getAttribute(this.realSrcAttribute);
if (realSrc) {
//装到lazyels数组中
if (this.lazyElements[realSrc]) {
this.lazyElements[realSrc].push(img);
} else {
this.lazyElements[realSrc] = [img];
}
}
//已经装入库中,过滤掉
return false;
}
//不符合条件的,从库中去除
return true;
}, this);
//设置定时器,根据平台不同,执行平率有所改变
this.lazyLoadTimeout = setTimeout(proxy(this._loadImage, this), isAndroid ? 500 : 0);
},
//
_loadImage: function() {
var img, realSrc, imgs;
//遍历lazyEls中的数组对够用
for (realSrc in this.lazyElements) {
//拿到一个数组对象
imgs = this.lazyElements[realSrc];
//拿到第一个图片对象,并从数组中移除
img = imgs.shift();
//如果图片数组中没有数据,则删除此数据对象
if (imgs.length === 0) {
delete this.lazyElements[realSrc];
}
//检测img对象的load事件
img.addEventListener('load', proxy(this._onImageLoad, this), false);
//设置img的src值
if (img.src != realSrc) {
this._setImageSrc(img, realSrc);
} else {
this._onImageLoad(img);
}
}
},
//加载图片时,触发
_onImageLoad: function(e) {
var me = this,
img = e.target || e,
realSrc = img.getAttribute(me.realSrcAttribute),
//再次拿到图片数组对象
imgs = me.lazyElements[realSrc];
//显示图片
me._showImage(img);
if (imgs) {
//遍历所有数组并加载显示每一个图片
imgs.forEach(function(i) {
me._setImageSrc(i, realSrc);
me._showImage(i);
});
//然后删除这个图片数据对象
delete me.lazyElements[realSrc];
}
},
//设置图片的地址,如果不是android的话,设置图片透明度为0
_setImageSrc: function(img, realSrc) {
if (this.useFade) {
img.style.opacity = '0';
}
img.src = realSrc;
},
//显示图片,设置图片已加载标记
//
_showImage: function(img) {
var me = this,
cb = function() {
img.setAttribute('data-lazy-load-completed', '1');
if (me.onImageLoad) me.onImageLoad(img);
};
if (me.useFade) {
img.style[vendor + 'Transition'] = 'opacity 200ms';
img.style.opacity = 1;
//监听动画事件
listenTransition(img, 200, cb);
} else {
cb();
}
},
// 设置图片区域
scan: function(ct) {
var imgs;
ct = ct || document.body;
//找到所有关联图片
imgs = ct.querySelectorAll('img[' + this.realSrcAttribute + ']') || [];
//
//为什么这一步??相当于转换为数据格式?
imgs = Array.prototype.slice.call(imgs, 0);
//过滤img标签
imgs = imgs.filter(function(img) {
//标签没有img或是已加载过的,remove
if (this.elements.indexOf(img) != -1 || img.getAttribute('data-lazy-load-completed') == '1') {
return false;
}
return true;
}, this);
//新加载的,合并到elements中
this.elements = this.elements.concat(imgs);
//执行加载方法
this._scrollAction();
},
//移除scroll,pageshow事件,清空数据
destroy: function() {
if (!this.destroyed) {
this.destroyed = true;
window.removeEventListener('scroll', this._onScroll_, false);
window.removeEventListener('pageshow', this._onPageShow_, false);
this.elements = this.lazyElements = null;
}
}
};
dummyStyle = null;
window.ImageLazyLoader = ImageLazyLoader;
//这里的window 可以改为this也可
})(window);
作者的代码只是对img标签做了监控,本人建议对非img标签也做监控,然后以扩展使用范围(本人有时会用背景图片显示图片,以自动比例宽高)。