参考:
懒加载,也可以叫延迟加载、惰性加载、按需加载。图片懒加载就是在长网页中延迟加载图像:用户滚动到它们之前,视口外的图像不会加载。
举个例子来说明,当打开淘宝首页的时候,只有在浏览器窗口里的图片才会被加载,当你滚动首页向下滑的时候,进入视口内的图片才会被加载,而其它从未进入视口的图像不会被加载。
- 使网页加载更快,用户体验更好
- 可以帮助减少并发请求,减轻服务器负载。
基本原理:先把img元素或是其他元素的背景图片路径设置为空或同一张图片(这样只用请求一次),而页面中的img元素如果没有src属性就不会发出请求去下载图片,只有当图片出现在浏览器的可视区域内时,才设置图片真正的路径,浏览器才会发出请求,让图片显示出来。
为了实现懒加载,我们需要先了解一些基本的知识,比如说如何获取某个元素的尺寸大小、滚动条滚动距离及偏移位置距离,如下图。
- 屏幕可视窗口大小:对应于图中1、2位置处
- window.innerHeight 标准浏览器及IE9+
- document.documentElement.clientHeight 标准浏览器及低版本IE标准模式
- document.body.clientHeight 低版本混杂模式
- 浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离:也就是图中3、4处对应的位置
- window.pagYoffset——IE9+及标准浏览器
- document.documentElement.scrollTop 兼容ie低版本的标准模式
- document.body.scrollTop 兼容混杂模式;
- 获取元素的尺寸:对应于图中5、6位置处
- el.style.width + el.style.padding + el.style.border + el.style.margin (使用style属性,这个元素必须已经有内嵌的样式,如
<div style="...."></div>
)
- el.style.width + el.style.padding + el.style.border + el.style.margin (使用style属性,这个元素必须已经有内嵌的样式,如
- 获取元素的位置信息:对应与图中7、8位置处
- 返回元素相对于文档document顶部、左边的距离;
- getoffsetTop()
- 返回元素相对于文档document顶部、左边的距离;
下面介绍几种实现方式:
- 页面滚动的时候计算图片的位置与滚动的位置,当图片出现在浏览器视口内时,将图片的 src 属性设置为 data-src 的值,这样,就可以实现延迟加载。
比较 image 的 offsetTop 与 seeHeight + scrollTop 的大小,当小于时则说明图片已经出现过在视口中,这时候继续判断图片是否已经替换过,如果没有替换过,则进行替换。
function lazyload() {
var images = document.getElementsByTagName('img');
var len = images.length;
var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
return function() {
var seeHeight = document.documentElement.clientHeight; //可视区域的高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条位置
for(var i = n; i < len; i++) {
if(images[i].offsetTop < seeHeight + scrollTop) { //offsetTop返回当前元素相对于其 offsetParent 元素的顶部的距离。
if(images[i].getAttribute('src') === 'images/loading.gif') {
images[i].src = images[i].getAttribute('data-src');
}
n++;
}
}
}
}
var loadImages = lazyload();
loadImages(); //初始化首页的页面图片
window.addEventListener('scroll', loadImages, false);
这种方法直接将函数绑定在 scroll 事件上,当页面滚动时,函数会被高频触发,这非常影响浏览器的性能。为了改善性能,接下来我们介绍优化后的第二种方法
- 在做事件绑定的时候,对 lazyload 函数进行函数节流(throttle)与函数去抖(debounce)处理。
简单来说:
- Debounce:一部电梯停在某一个楼层,当有一个人进来后,20秒后自动关门,这20秒的等待期间,又一个人按了电梯进来,这20秒又重新计算,直到电梯关门那一刻才算是响应了事件。
- Throttle:好比一台自动的饮料机,按拿铁按钮,在出饮料的过程中,不管按多少这个按钮,都不会连续出饮料,中间按钮的响应会被忽略,必须要等这一杯的容量全部出完之后,再按拿铁按钮才会出下一杯。
下面就是经过 throttle 处理后的代码:
function throttle(fn, delay, atleast) {
var timeout = null,
startTime = new Date();
return function() {
var curTime = new Date();
clearTimeout(timeout);
if(curTime - startTime >= atleast) {
fn();
startTime = curTime;
}else {
timeout = setTimeout(fn, delay);
}
}
}
function lazyload() {
var images = document.getElementsByTagName('img');
var len = images.length;
var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
return function() {
var seeHeight = document.documentElement.clientHeight;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(var i = n; i < len; i++) {
if(images[i].offsetTop < seeHeight + scrollTop) {
if(images[i].getAttribute('src') === 'images/loading.gif') {
images[i].src = images[i].getAttribute('data-src');
}
n = n + 1;
}
}
}
}
var loadImages = lazyload();
loadImages(); //初始化首页的页面图片
window.addEventListener('scroll', throttle(loadImages, 500, 1000), false);
demo2 可以很明显的看出有一定延迟
- 使用 IntersectionObserver API
目前有一个新的 IntersectionObserver API,可以自动"观察"元素是否可见,Chrome 51+ 已经支持。
function query(selector) {
return Array.from(document.querySelectorAll(selector));
}
var io = new IntersectionObserver(function(items) {
items.forEach(function(item) {
var target = item.target;
if(target.getAttribute('src') == 'images/loading.gif') {
target.src = target.getAttribute('data-src');
}
})
});
query('img').forEach(function(item) {
io.observe(item);
});
IntersectionObserver
传入一个回调函数,当其观察到元素集合出现时候,则会执行该函数。
io.observe
即要观察的元素,要一个个添加才可以。
io 管理的是一个数组,当元素出现或消失的时候,数组添加或删除该元素,并且执行该回调函数。
除了在加载图片时可以使用懒加载,路由和组件也可以通过懒加载来减轻页面负担,可以看这篇Vue懒加载