图片懒加载,前端的使命性能的优化
前言
前端工程师的一大使命就是做好性能优化
,例如我们的防抖、减少http请求次数
。例如学会利用缓存
,不要每次都重新做加载,那么今天就来分享一下懒加载,对非关键资源进行延迟加载,如图片、视频等。
懒加载
懒加载也叫延迟加载
,是一种优化网页性能
的技术。通俗来讲,懒加载就是只加载我们一屏可是范围内的资源
,其他的资源等到我们滚动下去,到我们可视范围内再进行加载
,例如淘宝京东、唯品会、拼多多等平台,我们进入页面,第一眼见到的资源是不需要判断的一定需要加载,但是后续的资源等到我们滑动屏幕之后,出现在屏幕内再进行加载,如果资源过多,不做懒加载,就会导致页面加载速度慢。如下图,刚进入页面,图片资源都会加载出来。
当我们迅速向下滚动时,就会发现他正在加载资源
懒加载的特点和优势
特点:
- 页面初始加载时,只加载当前视口内或即将进入视口的资源(如图像、视频等)。
- 其他不在视口内的资源会在用户滚动或进行其他操作导致其即将进入视口时才进行加载。 优势:
- 提高初始加载速度:减少了初始页面加载时需要传输的数据量,使页面能更快地呈现给用户。
- 节省带宽:避免一次性加载大量可能暂时用不到的资源。
- 提升用户体验:在用户无感知的情况下逐步加载资源,不会让用户长时间等待所有资源加载完成。
懒加载实现思路
- 图片加载就是我们的
img标签
中的src属性,浏览器会通过http请求向src发送请求,加载src路径中的资源,因此我们可以写一个数据属性,将真正要加载的资源放到数据属性data-src
中,而原本的src中就用一个1像素的图片(大小尽可能小的)去占位,这样就能让网页迅速加载完毕 - 监听事件,scroll,获取
scrollTop
滚动条距离顶部的高度,我们可以通过用户可视窗高度与滚动条距离顶部的距离两个数据进行比对,判断下一张图片是否需要加载。 - 但是
第一屏直接映入眼帘的图片我们应该让他直接显示出来。
因此我们可以做一个立即执行函数,把第一屏的内容直接展示出来。
懒加载实现过程
以多张图片为例子,第一屏的直接展示,后面的图片需要懒加载。
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载</title>
<style>
img {
display: block;
margin-bottom: 50px;
width: 400px;
height: 400px;
background: #ba1111;
}
</style>
</head>
<body>
<!-- 数据属性data-src 手动的触发下载 -->
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img95.699pic.com/photo/50050/3888.jpg_wh860.jpg">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190808/v2_1565254363234_img_jpg">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567641293753_img_png">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567640518658_img_png">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642423719_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642425030_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642425101_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642425061_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190904/v2_1567591358070_img_jpg">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567641974410_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567641974454_img_000">
</body>
</html>
- 我们将所有图片本身要加载的src都替换成了一个大小非常小的图片,这样就不会造成加载资源的负担
- 真正要加载的资源放到了
data-src
中,后续我们只需要通过js获取到data-src属性的值然后将值赋值给src就可以做到懒加载
js部分:
- 获取img标签
- 定义变量n,记录加载图片的数量
const imgs = document.getElementsByTagName('img');
const num = imgs.length;
let n = 0
- 监听scroll事件,回调函数为懒加载lazyload
window.addEventListener('scroll', lazyload)
- 懒加载lazyload
function lazyload() {
//可视区域的高度
let screenHeight = document.documentElement.clientHeight;
console.log(screenHeight);
//滚动条距离最顶部的距离,
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
console.log(scrollTop);
//判断图片是否存在在可视区域内
for (let i = n; i < num; i++) {
// 还没轮到图片加载
if (imgs[i].offsetTop > scrollTop + screenHeight) {
break;
} else {
// 图片该加载了
//主动触发下载
imgs[i].src = imgs[i].getAttribute('data-src');
//记录已经加载过的图片数量
n = i + 1;
if (n === num) {
//全部加载完毕后移除滚动事件
window.removeEventListener('scroll', lazyload);
}
}
}
}
- 主动触发加载就是获取到
data-src
然后把src
给替换掉进而加载图片 img[i].offsetTop
获取到图片距离顶部的距离,如果这个距离大于滚动条的距离加上一屏的距离,说明还没滚到他,不需要触发加载,否则触发加载- 加载一张,对n进行加一,
n表示加载图片的数量
,最后当图片全部加载结束,我们需要将监听事件移除
,当页面全部加载完毕了事件还在做监听,就没有意义了 - 由于第一屏的图片不需要进行监听,我们应该直接加载出来,因此需要做一次
立即执行
,提前触发一次lazyload函数,加载出第一屏图片。
做到这里懒加载确实结束了,但是程序还存在一些问题
- 例如:每次滚动事件触发,都会带来lazyload函数的执行,执行过程中都在不断的打印scrollTop...这些打印过于频繁,我们可以做一个节流进行优化,通过节流控制单位时间内触发的频率
- 我们的立即执行函数,应该放到什么位置?
节流函数
function throttle(func, limit) {
let inThrottle;
return function() {
const context = this;
const args = arguments;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
const throttleLazyload = throttle(lazyload, 200)
window.addEventListener('scroll', throttleLazyload)
但是我们需要秉持一个理念:不要重复造轮子(浪费时间)
就像这个节流函数不需要我们每次要用就去重新写一遍一样,我们可以直接去借用已经封装好了的。引入loadsh库
,这个库帮我们封装了很多日常工作中实用的工具函数,每次需要用只要去官网查看一下他的使用手册就可以了
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
整份js
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js" ></script>
<script>
const imgList = document.getElementsByTagName('img');
const num = imgList.length;
// console.log(imgList);
let n = 0;
// 注册在scroll事件中的触发函数
const throttleLazyload = _.throttle(lazyload, 200)
window.addEventListener('scroll', throttleLazyload)
// DOMContentLoaded html和css页面加载完成时触发,保证第一次页面的图片不用滚轮加载图片
document.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded');
lazyload()
})
// 在DOMContentLoaded之后
window.addEventListener('load', () => {
console.log('load');
})
function lazyload() {
// 获取可视区域一屏的高度
let screenHeight = document.documentElement.clientHeight;
console.log(screenHeight);
// 获取滚动条滚动的距离 多浏览器适配
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
console.log(scrollTop);
for (let i = n; i < num; i++) {
if(imgList[i].offsetTop < screenHeight + scrollTop) {
imgList[i].src = imgList[i].getAttribute('data-src');
// 记录已经加载过的图片的下标
n = i + 1;
if(n === num) {
// console.log('所有图片加载完成');
window.removeEventListener('scroll', throttleLazyload)
}
}
}
}
// 不要去重复造轮子
// function throttle(func, limit) {
// let inThrottle;
// return function() {
// const context = this;
// const args = arguments;
// if (!inThrottle) {
// func.apply(context, args);
// inThrottle = true;
// setTimeout(() => inThrottle = false, limit);
// }
// };
// }
</script>
回到刚刚那个问题,先触发一次懒加载函数应该在什么时候触发?
// DOMContentLoaded html和css页面加载完成时触发,保证第一次页面的图片不用滚轮加载图片
document.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded');
lazyload()
})
// 在DOMContentLoaded之后
window.addEventListener('load', () => {
console.log('load');
})
第一次触发,应该在DOMContentLoaded事件
执行结束后,load事件
结束前触发。DOMContentLoaded
事件是页面文档加载并解析完毕,例如(HTML、css加载完毕),而load事件
是页面的全部资源(包括图片、视频等)加载完。
这种先触发 DOMContentLoaded
事件再触发 load
事件的顺序,可以让我们在页面的 DOM 结构已经构建完成,但其他资源(如图片)可能还在加载的情况下,先进行一些操作,比如获取 DOM 元素、初始化页面等。而 load
事件则可以用于在所有资源加载完成后执行一些需要依赖全部资源的操作,比如图片加载完成后的处理。理解生命周期事件有利于我们对代码思想的提升
,找到一个最佳的执行时机
。
小结
不要重复造轮子
(loadsh库提供了很多使用工具函数)- 要对代码
逻辑具有敏感性
,当图片全部加载完成之后,不需要再进行scroll事件监听 - 做好前端
性能优化
,做懒加载data-src
存放真实地址,src
用小图片占位- 通过图片的
offsettop
与scrollTop + clientHeight
对比,确认是否该加载下一张图片 - 做好
防抖节流
转载自:https://juejin.cn/post/7379818614361980938