前言
影响网页性能最主要的因素应该属图片资源了,现在一张图片动不动几兆,如果一张页面有很多图片(像电商类网站),等页面上的图片全部加载完毕再显示在浏览器上的话,用户可能早就不耐烦地关掉网页了。解决该问题的办法就是图片懒加载了,即只有用到的时候再加载图片,避免无意义的开销。
懒加载是一个理念,具体的应用场景不止一个:
- 优先加载可视区域的内容,其它部分等进入了可视区域再加载;
- 在单页应用中,未显示的tab页不加载图片;
我们讨论的是场景1,对于场景2,其实vue这些组件化方案已经默认处理掉了。
原理解析
一张图片是否加载,是由其是否具有src属性决定的,没有src属性时,浏览器就不会发起图片加载请求。这是我们实现图片懒加载的切入点。
接下来我们来解决如何判断当前图片是否已经在可视区域中的问题。这里要用到几个API:
- document.documentElement.clientHeight:获取屏幕可视区域的API(包含内边距,不包含水平滚动条、边框和外边距);
- document.documentElement.scrollTop:获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离。
- element.offsetTop:获取元素相对于文档顶部的高度;
根据上图我们可以很快地知道,当满足offsetTop-scrollTop<clientHeight的时候,就可以加载图片了。
代码实现
img {
display: block;
width: 100%;
height: 300px;
margin-bottom: 20px;
}
<img data-src="./images/1.jpg" alt="">
<img data-src="./images/2.jpg" alt="">
<img data-src="./images/3.jpg" alt="">
<img data-src="./images/4.jpg" alt="">
<img data-src="./images/5.jpg" alt="">
<img data-src="./images/6.jpg" alt="">
<img data-src="./images/7.jpg" alt="">
<img data-src="./images/8.jpg" alt="">
<img data-src="./images/9.jpg" alt="">
<img data-src="./images/10.jpg" alt="">
<img data-src="./images/1.jpg" alt="">
<img data-src="./images/2.jpg" alt="">
// 文档加载完毕时注册懒加载功能
window.onload = lazyLoad;
// 给所有的图片加上懒加载功能
function lazyLoad() {
// 图片信息列表,列表元素的格式为(offsetTop, imageSrc, dom)
const imageMetaList = [];
// 初始化imageMetaList
const images = document.getElementByTagName('img');
for(let i=0; i<images.length; i++) {
let dom = images[i];
let offsetTop = getOffsetTop(image);
let imageSrc = dom.getAttribute('data-src');
imageMetaList.push({
offsetTop, // 保存offsetTop,避免每次都要去重新计算
imageSrc,
dom
})
}
// 获取视口的高度
const clientHeight = document.documentElement.clientHeight;
// 注册滚动监听
window.onscroll = function() {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
imageMetaList.forEach(imageMeta=>{
if(imageMeta.offsetTop - scrollTop < clientHeight) {
imageMeta.dom.src = imageMeta.imageSrc
}
});
}
}
// 获取元素的offsetTop
function getOffsetTop(e) {
let result = 0;
while(e !== undefined) {
result += e.offsetTop;
e = e.offsetParent; //offsetTop是元素与offsetParent的距离,循环获取直到页面顶部
}
return result;
}
改进1
js中还有一个更好的API:
- getBoundingClientRect():返回元素的大小以及其相对于视口的位置。
有了这个API之后,我们可以用如下方式来判断图片是否进入视口:
function isInVisualArea(el) {
var bound = el.getBoundingClientRect();
var clientHeight = window.innerHeight;
return bound.top <= clientHeight;
}
有了这个方法之后,就不需要像上面的实现一样加一个缓存列表了。新的实现如下:
// 文档加载完毕时注册懒加载功能
window.onload = lazyLoad;
// 给所有的图片加上懒加载功能
function lazyLoad() {
const images = document.getElementByTagName('img');
// 获取视口的高度
const clientHeight = document.documentElement.clientHeight;
// 注册滚动监听
window.onscroll = function() {
images.forEach(image=>{
if(isInVisualArea(image)) {
image.src = image.getAttribute('data-src');
}
});
}
}
// 判断是否在可视区域
function isInVisualArea(el) {
var bound = el.getBoundingClientRect();
var clientHeight = window.innerHeight;
return bound.top <= clientHeight;
}
改进2
onscroll的触发频率非常高,如果每次触发onscroll都去检查是否要加载图片比较吃性能,可以考虑加入节流机制,减少开销。
使用lazysizes
下载:
cnpm install lazysizes -S
引入:
import 'lazysizes'; // 导入插件
import 'lazysizes/plugins/parent-fit/ls.parent-fit'; // 注意:切勿从npm包中导入/获取* .min.js文件
使用:
<img
class="lazyload"
data-src='https://placehold.it/600x300//f00?text=BaymaxCSDN 1-懒加载'
alt=''/>
如果担心禁用JavaScript,可以结合noscript使用:
<!-- noscript pattern -->
<noscript>
<img src="image.jpg" />
</noscript>
<img src="transparent.jpg" data-src="image.jpg" class="lazyload" />
更多的功能,如响应式图片、模糊图形、透明图形、加载动画等,见参考资料1。
使用vue-lazyload
npm install vue-lazyload -S
<img v-lazy="src"/>