最近接手一个项目 。 结果光安装依赖都出现了一堆 麻烦 。
好不容易处理完一个 , 又来一个 。头疼啊
看到之前有一些预加载的学习笔记。于是又查查找找 ,想想写写 把预加载和懒加载的笔记写完整
发现制图挺麻烦的!不知道你们有没有什么推荐?
写了挺久的这篇文章,有什么不对的地方欢迎评论或私聊指出
图片的预加载和懒加载
预加载和懒加载的字眼总会看到 。 其实预加载和懒加载不仅仅是用于加载图片资源。其他资源,文字,视频。都可以。
我们较常用或较需要使用的场景就是加载图片资源。
这里,我们也只讨论加载图片资源。
图片预加载
什么是图片的预加载?
提前加载所需要的图片资源,加载完毕后会缓存到本地,当需要时可以立马显示出来,以达到在预览的过程中,无需等待直接预览的良好体验。
简而言之 : 就是需要显示前先加载
什么场景可能需要使用预加载
- 漫画
我们知道漫画是一张一张连续的图片组成 。我们在看漫画时,也是一张一张看的。
但如果当我们看完一张,切换到下一张时再加载图片,那么就会有一段空白的加载时间。而且漫画图片一般比较大,如果网络不是很好,那么加载时间就会比较久。体验就会下降。
正如图片所示,如果我们看完 2 了 ,想看 3 ,3 却还没加载完就会大大降低体验感。
而如果能在我们预览 2 这段时间里就提前加载好 3 , 等到我们看 3 时就直接里面显示。那么就体验会好很多。
- 图片画廊
希望用户预览图片时能够顺畅 ,而不是看到下一页的图片了,上一页的还没加载出来。
与漫画类似,但加载的图片资源会比较多。
- 或者其他加载会比较耗时但不希望让用户看到加载时空白的场景
如要一次性显示好几张图片 ,如果让用户在看的时候加载,那么图片先后显示的过程,和出现空白的现象会让用户看见。
特别是网络不好时,用户看见空白不知道是根本没有图片,还是加载不出来。
如果我们使用预加载,先显示一个进度条,告诉用户加载了多少了,当加载了百分之百后再一起全部显示出来。
那么用户起码不会看到空白,也知道是在加载中,并且根据进度条能大概判断一下加载速度。那么体验就会上升。
预加载的缺点
-
预加载会占用较多的后台资源,因为可能一次性加载较多的图片
-
预加载需要比较长的时间 ,一般是利用用户进行其他操作时进行。(如漫画是在用户看上一个图片时进行预加载 ) 。 或者是在等待的这段时间显示其他。(如显示进度条。)
图片预加载的方法
这里介绍两个常用且实用的方法 。每个方法都有优缺点 。 也有自己使用的场景。我们应该根据实际选择合适的方法
1. 通过 CSS
步骤
- 创建用来预加载的标签
- 给标签使用背景图,背景图的路径是需要预加载的图片资源。并且将图片移到看不见的地方,或缩小到看不见。不能用 display=none , 否则预加载会失效
- 当使用到已经预加载好的图片时,会直接使用缓存好的图片资源,而不需要向服务器发送请求。
demo
<!--HTML文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSS 预加载</title>
<!-- 引入预加载样式文件 -->
<link rel="stylesheet" href="./preLoad.css">
</head>
<body>
<!-- 点击时显示图片 -->
<button οnclick="show()">显示</button>
<ul>
<!-- 用来预加载的标签 -->
<li id="preImg1"/>
<li id="preImg2"/>
<li sid="preImg3"/>
</ul>
</body>
<script>
var show = function () {
// 点击 ul 里的标签 ,显示图片
document.querySelector("ul").innerHTML = `
<li><img src="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"></li>
<li><img src="https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552"></li>
<li><img src="https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg"></li>`
}
</script>
</html>
/* 预加载样式文件 */
img {
width: 200px;
}
/* 预加载文件 */
#preImg1 {
background-image: url('https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg');
width: 0;
height: 0; /* 隐藏用来预加载的标签 */
}
#preImg2 {
background-image: url('https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552');
width: 0;
height: 0;
}
#preImg3 {
background-image: url('https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg');
width: 0;
height: 0;
}
分析 :
在游览器第一次打开这个文档时,加载 CSS 过程中遇到需要加载的背景图片资源,于是会发送请求到服务器,服务器返回图片资源,游览器本地缓存。
在游览器的开发者根据(F12) 的 NewWork 项我们可以看到请求码是 200 。是服务器返回的新的响应。
此时我们先清空游览器请求资源记录
然后点击显示按钮,将填写了 src 的图片标签放到文档中。显示图片
图片里面显示,而 newWork 栏里也没有向服务器发送新的请求,因为服务器就有缓存。即使有 状态码也是 304 (表示本地资源)
2. 通过 JavaScript
步骤
- 将需要预加载的图片资源的 URL 保存在数组里
- 循环遍历 URL 数组执行以下步骤,直到结束
- 创建一个 image 对象
new Image()
- 将 image 对象的 src 属性的值指定为预加载图片的 URL
demo
<!-- HTML 文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS 预加载</title>
<!-- 简单的样式,不用关注 -->
<style>
ul {
float: left;
margin: 0;
padding: 0;
}
li {
list-style-type: none;
float: left;
}
img {
width: 100px;
height: 100px;
}
#mask {
position: absolute;
width: 300px;
height: 100px;
background-color: rgb(177, 177, 177);
line-height: 100px;
text-align: center;
color: rgb(255, 255, 255);
font-size: 32px;
font-weight: 600;
}
</style>
</head>
<body>
<!-- 设置遮罩层,显示加载进度 -->
<div id="mask">00%</div>
<ul>
<!-- 写好标签,会解析到此时会立马加载 -->
<li><img src="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"></li>
<li><img src="https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552">
</li>
<li><img src="https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg"></li>
</ul>
</body>
<!-- 引入预加载脚本文件 -->
<script src="./preLoad.js"></script>
</html>
// 预加载脚本文件
// 预加载的图片 URL
const urlList = [
'https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg',
'https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552',
'https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg']
// 预加载函数 ,会在加载完毕后隐藏遮罩层
function preLoad() {
let process = document.querySelector('#mask'), // 遮罩层 ,用于修改进度和隐藏
len = urlList.length,// 提出 URL 数组长度 ,提供性能
count = 0, // 计算已加载数量和修改进度
ul = document.querySelector("ul"), // 将图片放入此
imgList=""; // img 标签字符串临时存放点,避免刷新DOM的次数。
// 为了模拟多图片资源和将加载过程慢下来,这里使用了计时器 , 实际情况我们采用遍历 URL 数组。
let id = setInterval(() => {
let img = new Image()
img.src = urlList[count]
imgList += `<li><img src="${urlList[count]}"></li>`
img.onload = img.onerror = function () {
count++;
process.innerText = (count / len * 100).toFixed(2) + '%'; // 设置进度百分比
if (count === len) {
clearInterval(id)
ul.innerHTML += imgList;
process.style.display = 'none'
}
}
}, 500)
}
// 调用预加载函数
preLoad()
分析
当需要显示大量图片时,我们先显示其他或者有一些加载的提示。同时执行预加载函数。这里我们使用了遮罩层和进度显示
预加载函数会加载图片资源并本地缓存,不会显示。但有缓存。在加载的过程中,随着加载完成的数量增加,进度增加。
当全部加载完毕后,将图片动态添加到页面上,隐藏遮罩层。图片可被立即显示,无需再发送请求到服务器请求资源。
预加载分类
无序预加载
如预览大量图片时,或表情包,它关心的是什么时候全部加载完,然后一起显示出来,其顺序不重要。
表情包的预加载
const srcList = ['src1','src2','src3',...]
function preLoad() {
let count = '';
for (let i = 0; i < emojiSrcs.length; i++) {
var img = new Image()
img.src = srcList[i]
img.onload = img.onerror = function () { // 无论加载成功还是失败都会执行
count++
if (count == emojiSrcs.length - 1) { // 如果全部加载完毕
mask.style.display = "none"; // 隐藏遮挡层
emoji.style.display = "block"; // 显示emoji
}
}
}
}
preLoad()
有序预加载
如漫画,它不仅仅关系什么时候全部加载完,还有需要有一定的顺序。
// 使用全局变量,保存计数
var count = 0
const emojiSrcs = ['src1','src2','src3',...]
function preLoad() {
var img = new Image()
img.src = emojiSrcs[count]
count++
// 加载成功和失败都会执行
img.onload = img.onerror = function () {
if(count === emojiSrcs.length)
return
preLoad()
}
}
preLoad()
// 当第一张加载后会加载第二张,第二种加载后再加载第三张,于是可以在我们预览第一张时就可以预先加载第二张,第三张
// 相比无序加载,不需要等使用加载完毕才能预览,而且不会预览到第二张时,第二张没加载好,但是第三张或更后的图片先加载了。
图片的懒加载
什么是懒加载
图片的懒加载是等图片在用户要看到时才进行加载 。
与预加载相反 。 预加载可以说是勤劳,先做完然后万事大吉 。 而懒加载却是需要我再做 , 不要我就准备着。
简而言之 懒加载就是 先显示再加载
什么场景需要懒加载
- 电商
我们知道大量商品展示是分页请求的 。也就是我们会看到拉到底了再重新加载。但屏幕看到的仅是一页请求内容的部分内容。
电商搜索产品时图片会有很多,但单个图片资源又不大。因此适合应用懒加载。避免不必要的图片的加载和请求。
如 :当我们在某宝,某东,某夕夕买东西时,假设搜索完后看到一个进度条,告诉我们还在加载。或者是显示一堆空白,然后先加载出来的先显示(但可能需要往下翻一翻) 。可是 ,我们可能选择的是第一家店 。 那么其他的就并不需要加载了。
- 其他图片较多场景
这与画廊在线图库这里场景不一样 。 图片多可能主要显示的不仅仅是图片 。 如显示大量文章,文章有一些配图 。
懒加载的缺点
- 需要监听图片是否显示,比较耗游览器性能。
- 图片是显示时才去加载。如果网络不太好可能会有一段时间是空白
图片懒加载的方法
原理
利用自定义属性将图片的 URL 存放到图片标签身上 , 图片的 src 为空或者用其他较小的图片资源代替(提示加载中)
图片的懒加载主要是监听 body 或者其他存放图片且滚动的元素的 scroll 事件.
在每一次事件触发时,通过相关的 API 和属性,检查图片是否显示 。
如果显示就将 URL 填到 src 属性中 ,加载图片 。如果没有显示则不进行操作。
如何检测图片是否显示
对于理解懒加载的原理其实不难 。 懒加载的关键点也是重难点就在于如何检测图片是否处理可视区内。
下面借助两张图来理解和学习。
- clientHeight : 可视区高度
可视区高度可使用 window.innerWidth
获得 。但这个属性 IE9 以下并不支持。 因此兼容性写法为:
let clientWidth=window.innerWidth|| document.documentElement.clientWidth|| document.body.clientWidth;
- offsetTop
子图片相对父元素的偏移量。let offsetTop = imgNode.offsetTop
- scrollTop
滚动元素顶部被滚出显示区的高度。 let scrollTop = scrollNode.scrollTop
**注意 ,这个元素必须是有滚动条才可以,否者是 0 **
如果滚动区域是整个页面,也就是仅 HTML 出现了滚动条 ,其他不设置滚动条 。
那么通过 document.documentElement.scrollTop
来获取 scrollTop 的值
一般,我们只检查整个页面被隐藏部分
如上图所示 : 当 scrollTop + clientHeight > offsetTop
时 , 图片便是显示出来或者在可视区之上了。
- offsetHeight : 图片节点的宽高
可以通过设置好的高度自己设置。写死。
或者
element.clientHeight // 内容加内边距
element.offsetHeight // 内容加内边距加边框
如上图所示 :当 offsetTop + offsetHeight > scrollTop
时 , 图片就在可视区内或可视区下。
结合两个图
if ( v.offsetTop < clientHeight + scrollTop && v.offsetTop + v.offsetHeight > scrollTop) {
// 图片在显示区内
}
demo
// 懒加载
// 参数 : 需要进行预加载的图片的 id (#id) , 类名(.class) ,或标签名(img)
export default function lazyLoad(imgSelector) {
const clientHeight = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, // 兼容获取
scrollTop = document.documentElement.scrollTop; // 获取页面被卷出去的顶部的高度大小
let imgsNodes = document.querySelectorAll(imgSelector); // 获取预加载图片列表
// 遍历检查图片是否在可视区内
imgsNodes.forEach((v) => {
if (
v.offsetTop < clientHeight + scrollTop &&
v.offsetTop + v.offsetHeight > scrollTop
) {
// 设置正确的 src
// 实际使用时
// v.src = v.getAttribute("srcString");
// 这里使用计时器模拟加载时间 ,为能看到是先显示再加载的效果 。
setTimeout(() => {
v.src = v.getAttribute("data-srcString");
}, 800);
}
});
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>懒加载</title>
</head>
<style>
img {
width: 300px;
height: 300px;
display: block;
margin: 0 auto;
border: 1px solid #ccc;
}
</style>
<body>
<div>
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
<img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
</div>
</body>
<script type="module">
// 导入模块
import lazyLoad from './lazyLoad.js'
// 使用 onl oad , 以免文档没有加载完就进行操作
window.onload = function(){
// 先调用一次,将可能在显示区的照片先加载
lazyLoad('img')
// 监听滚动事件
window.addEventListener('scroll',lazyLoad.bind(this,'img')) // 利用 bind() 传参
}
</script>
</html>
懒加载优化
前面我们提高懒加载的缺点 , 它对服务器友好,对游览器却不友好。
因为一直监听滚动事件,一直触发是非常消耗性能的 。
对此我们可以进行防抖和节流来减少性能的消耗 。提高流畅度。
节流
图片是有一定的宽高的,并不是稍微移动一点点就进行获取图片节点呀 然后遍历呀 。
其他不变,只修改 HTML 的 script 标签内的代码
<script type="module">
import lazyLoad from './lazyLoad.js'
window.onload = function(){
lazyLoad('img') // 先执行
let stop = true; // 节流阀
function throttle (){ // 节流函数
if(stop){ // 如果被节流了
setTimeout(() => {
stop = false
console.log('节流');
}, 100);
return
}
stop=true
lazyLoad('img');
return;
}
window.addEventListener('scroll',throttle) // scroll 的执行函数改为节流函数
}
</script>
当滑动滚动条时 , 可以看到 节流时输出节流 和 执行时输出执行 是有交替的 。 达到了一定的节流效果。
防抖
为什么要防抖,因为可能有以下这个场景 。我们知道 我们要的图片得往下滑好几页 。 所以前面的几页图片是可以不需要显示的。
或者前面的之前我们已经预览过了,我希望预览比较靠后的图片。那么前面的也可以不用去加载了。
那么这种情况就可以使用防抖技术了。
其他不变 ,在原来的节流函数进行小修改
let stop = true;
let timeID = ''; // 增加一个变量保存倒计时器的 ID
function throttle (){
if(stop){
setTimeout(() => {
stop = false
}, 100);
return;
}
stop=true
// 删除上一个倒计时器
clearTimeout(timeID)
// 重新设置倒计时器
timeID = setTimeout(()=>{
lazyLoad('img');
return
},100) // 延缓 0.1 秒后加载
}
但防抖大部分情况是不需要的,除非在防抖这段时间内的空白无太大影响
参考文章