从需求出发:
在实际的项目开发中,我遇到了一个这样的需求:一个页面模块有很多列表数据展示,每条数据都带有图片,而首次展示的图片只需要不到10张,那么我们还要一次性把所有图片都加载出来吗?显然这是不对的,不仅影响页面渲染速度,还浪费带宽(因为需要对列表进行拖动排序,需加载出全部列表,不能做分页)。我们可以在浏览器滚动到一定的位置的时候进行下载,这也就是们通常所说的惰性加载,技术上现实其中要用的技术就是图片懒加载--到可视区域再加载。
实现方案:
1、默认不加载图片,只加载占位符
2、组件滚动条变化
3、计算可视区域,触发条件
4、<img>标签src属性加载资源
知识点:
scrollTop:外框元素的滚动高度
offsetTop:元素相对于最近的包含该元素的定位元素(具有position属性且不是static)边框的距离。如果没有定位的元素,则默认body。
offsetHeight:它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。
计算:可视区域的高度(offsetHeight) + 滚动条卷去的高度(scrollTop) >= 元素相对于外框的距离(offsetTop) - 偏移量 (提前加载)
代码实现:
页面结构
<style type="text/css">
.container{
width:200px;
height:200px;
position:relative;
overflow-y:scroll;
}
.img-area{
width:100px;
height:100px;
}
</style>
<div class="container">
<div class="img-area">
<img class="pic" alt="loading" data-src="./img/img1.png" src="image-placeholder-logo.svg">
</div>
<div class="img-area">
<img class="pic" alt="loading" data-src="./img/img2.png" src="image-placeholder-logo.svg">
</div>
<div class="img-area">
<img class="pic" alt="loading" data-src="./img/img3.png" src="image-placeholder-logo.svg">
</div>
<div class="img-area">
<img class="pic" alt="loading" data-src="./img/img4.png" src="image-placeholder-logo.svg">
</div>
<div class="img-area">
<img class="pic" alt="loading" data-src="./img/img5.png" src="image-placeholder-logo.svg">
</div>
</div>
src属性统一用一个占位图片,alt属性是在图像无法显示时的替代文本。 data-src是自定义属性,用来保存实际的图片地址,可以通过HTMLElement.dataset来访问。
脚本代码:
var container = document.querySelector('.container');
container.onscroll = function(){
checkImgs();
}
function isInSight(el) {
var sTop = container.scrollTop;
var oHeight = container.offsetHeight;
var oTop = el.offsetTop;
return sTop + oHeight > oTop;
}
function checkImgs() {
var imgs = document.querySelectorAll('.pic');
Array.from(imgs).forEach(el => {
if (isInSight(el)) {
loadImg(el);
}
})
}
function loadImg(el) {
var source = el.dataset.src;
el.src = source;
}
checkImgs();
可以看出,页面加载时候,绑定外框的scroll事件,随着用户向下滚动鼠标,把img的src赋予新的值,网络重新发起请求,拉取图片。这里应该是有一些可以优化的地方,比如
1、可以只监听向下滚动时候的事件,并设置延时(使用截流函数),防制多次调用回调函数。
2、可以设一个标识符标识已经加载图片的index,当滚动条滚动时就不需要遍历所有的图片,只需要遍历未加载的图片即可。
3、可以在计算的时候,增加偏移数据,提前加载图片,并使用淡入效果,提高流畅性。
另一种计算方法:
getClientRects()方法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。包含边框的只读属性left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。
这种条件下,假设bound = el.getBoundingClientRect(),随着滚动条的向下滚动,bound.top会越来越小,也就是图片到可视区域顶部的距离越来越小,当bound.top===clientHeight时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。
也就是说,在bound.top<=clientHeight时,图片是在可视区域内的。
function isInSight(el) {
var bound = el.getBoundingClientRect();
var clientHeight = window.innerHeight;
return bound.top <= clientHeight;
}
进一步考虑:
以上监听scroll,并计算元素位置来实现惰性加载。当数据达到一定量的时候,事件绑定和循环位置计算会消耗大量的性能,每次调用 getBoundingClientRect() 都会强制浏览器 重新计算整个页面的布局 ,可能给你的网站造成相当大的闪烁。这种方式有些美中不足。
交叉观察器:
IntersectionObserver 就是为此而生的,它是HTML5新增的api,可以检测一个元素是否可见,IntersectionObserver 能让你知道一个被观测的元素什么时候进入或离开浏览器的视口。
它兼容性有限,
Chrome 51+(发布于 2016-05-25)
Android 5+ (Chrome 56 发布于 2017-02-06)
Edge 15 (2017-04-11)
iOS 不支持
不过不用担心,WICG 提供了一个polyfill,可以兼容到以下版本:
它的用法也很简单,类似于rxjs中的observe。
var observe = new IntersectionObserver(callback, option);
IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(可选)。返回一个观测实例observe,可以指定观测哪个DOM节点。
// 开始观察
observe.observe(document.getElementById('example'));
callback = function(entries){
entries.forEach((entry) => {
if (entry.isIntersecting) {
//开始进入,交叉状态,在此处理图片逻辑。
} else {
//已完全进入或完全离开
}
});
}
// 停止观察
observe.unobserve(element);
// 关闭观察器
observe.disconnect();
entries是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。isIntersecting,返回一个布尔值, 如果目标元素与交叉区域观察者对象的根相交,则返回 true 。如果返回 true,则描述了变换到交叉时的状态;如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态。
IntersectionObserverEntry对象提供了很多有用的属性,比如target是被观察的目标元素,是一个 DOM 节点对象,intersectionRatio是目标元素的可见比例,即DOM节点的可见面积和总面积的比例,完全可见时为1,完全不可见时小于等于0,可以通过此属性设置图片的透明度,做成淡出的效果。
下拉无限滚动:
在页面底部有一个loading状态标签。一旦标签可见,就表示用户到达了页面底部,从而加载新的条目放在标签的前面。这样做的好处是,比监听scroll和计算节省了很多性能消耗,现有IntersectionObserver可以很简单的应用。下面是实现方法:
var intersectionObserver = new IntersectionObserver(
function (entries) {
// 如果不可见,就返回
if (entries[0].intersectionRatio <= 0) return;
//在此加载新的数据
});
intersectionObserver.observe(document.getElementById('loading'));
小结:
图片(不只有图片,主要是图片占用的资源最多最常见)惰性加载是一种网页优化技术。通过多种方案对比,使图片仅在浏览器当前视窗内出现时才加载该图片,达到减少首屏图片请求数,优化前端性能,提高用户体验。不管哪种方法,都有其自己的优势和劣势,掌握其中的原理,灵活应用才是最重要的。这对开发中遇到的问题及解决方法进行了总结,都是实战得来的经验,描述不清或者不对的地方,请多多指教。