基础业务:图片懒加载
背景
大部分场景下我们对懒加载的定义实际上是对于图片而言的,对于图片进入可视区域之后去请求图片资源的这种情况、这种case实际上就是一个典型的懒加载的case。这些资源虽然是HTML DOM上的,但是这些资源没有进入可视区域之前,这些图片资源并没有由浏览器发出请求去请求这些图片资源,为什么呢?
对于类似电商这种图片很多,页面很长的业务场景下呢,我们希望相关的资源是要进行懒加载的,原因就是首先它能减少无效资源的加载
。假如说我们由一张图片,但是用户只看了两页几十张就退出界面了,剩下的绝大部分图片没有进入到用户的可视区域,用户没有去看。这种情况下,我们就没有必要去加载那么多的图片资源的。这些资源对用户访问来说是无效资源,因为他们根本就没看,那么请求这些资源也是无效的请求。
第二点就是浏览器对于某一个host name是有并发度上限的,如果说我们的图片所在的CDN和我们的静态资源所在的CDN是同一个CDN的话,我们图片的加载就会阻塞后续JS的加载。JS大部分情况下都是写在body尾部的,那么img大多数情况下会在JS的上面,如果浏览器在并发请求图片的时候达到并发度的限制的话,会导致浏览器没有办法并发请求到后面的JS相关的资源,从而导致网站JS加载的延后,进而影响网站JS相关逻辑的正常使用
。
图片什么时候会向服务器或者是CDN去请求资源呢,只有img标签里的src属性被设置之后,浏览器在渲染过程中解析到img下的src属性,就会去请求src资源。所以在真实场景下呢,当图片进入可视区域之后,img的src才会被设置进去,而不是说页面一开始加载的时候,可视区之外的img的src属性就被设置进去。如果已经设置了的话,那么显然图片就会被直接的加载进来,也就不存在懒加载的问题了。
原理
懒加载的实现就是对于没有进入界面可视区域内的图片,img的src并不是这个图片的真正URL,可能是一个1像素的占位符,图片真正的URL被存放在img标签的一个自定义属性上。就先叫data-url吧,因为这个地址没有放到SRC上,所以不会造成img向服务端或者CDN去请求图片资源。当图片通过JS逻辑,可能是监听scroll事件,当scroll事件触发到图片已经进入到界面可视区域内,讲img标签上的data-url属性中的URL地址放置到img的src中,然后src的变化就会触发相关图片的资源请求,这种请求机制是延后的,通过监听scroll事件动态设置src
。
原生实现
首先,将图片的src属性置空,将图片的URL放置在img的一个自定义属性data-original
中。
实现来说大体的思路就是先获取屏幕的高度和img图片距离屏幕顶端的高度,当图片距离顶端的高度小于屏幕的高度时候,将图片中存储图片URL的属性拿出来赋值给src就可以了。
var viewHeight = document.documentElement.clientHeight
function lazyload () {
var eles = document.querySelectorAll('img[data-original][lazyload]')
Array.prototype.forEach.call(eles, function (item, index) {
var rect
if (item.dataset.original === '')
return
rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
!function () {
var img = new Image() // 必要的!
img.src = item.dataset.original
img.onload = function () {
item.src = img.src
}
item.removeAttribute('data-original')
item.removeAttribute('lazyload')
}()
}
})
}
lazyload()
document.addEventListener('scroll', lazyload)
这里要注意的一个点:就是先要执行一下lazyload
,否则首屏的几张图片不触发scroll事件是不会被加载出来的。
有人把代码粘贴过去试了下,发现图片是乱的,也没有出现什么懒加载。
那是因为我们没有给图片设置高度,图片都以特别矮的高度展现,肯定是集中再首屏了,当然不会出现什么懒加载。
加了一下CSS样式。
再次运行看看:
ok效果很明显。
zepto插件
zepto也有插件实现了lazyload:
;(function($) {
function visible(elem){
elem = $(elem)
return !!(elem.width() || elem.height()) && elem.css("display") !== "none"
}
$.fn.lazyload = function(threshold, callback) {
// 兼容参数写法
if($.type(threshold) === 'object') {
threshold = threshold.threshold;
}
if($.type(threshold) === 'function') {
callback = threshold;
threshold = 0;
}
var $w = $(window),
th = threshold || 0,
retina = window.devicePixelRatio > 1,
attrib = retina? "data-original-retina" : "data-original",
images = this,
loaded;
this.one("lazyload", function() {
var source = this.getAttribute(attrib);
source = source || this.getAttribute("data-original");
if (source) {
this.setAttribute("src", source);
if (typeof callback === "function") callback(this);
}
});
function lazyload() {
var inview = images.filter(function() {
var $e = $(this);
if (!visible($e)) return;
var wt = $w.scrollTop(),
wb = wt + $w.height(),
et = $e.offset().top,
eb = et + $e.height();
return eb >= wt - th && et <= wb + th;
});
// 触发加载
loaded = inview.trigger("lazyload");
// images剔除已加载的元素
images = images.not(loaded);
}
$w.on("scroll.lazyload resize.lazyload lookup.lazyload", lazyload);
lazyload();
return this;
};
})(window.Zepto);
原理和刚才我说的也差不多,多了很多差错处理还有健壮性的代码,总体思路都是一样的。
用法就是先用zepto选择器选择需要进行懒加载的图片,然后执行lazyload
方法就可以了,前提是要引入zepto及其懒加载相关的插件。
$('img[data-original][lazyload]').lazyload()
具体的演示就不做了,跟上面的一样。
如果大家在前端其他框架中使用懒加载,也会有其他的一些工具去支持,不过还是要清楚原理的,在前端轮子这么多的时代,只有特别扎实的HTML、CSS、JS底层基础还有深厚的计算机基础才能在前端的大潮中站稳脚步。