前言
在当今的数字时代,网页的加载速度和用户体验已经成为网站成功的关键因素。在我们日常浏览某些购物网站淘宝京东这种,我们有时候会遇到从上往下滑,图片显示正在加载中这种情况或者进入页面的时候有个loading,这就是我们接下俩要说的两兄弟懒加载和预加载。
1. 懒加载
顾名思义啊,这个懒加载就跟它的名字一样是个有点懒惰的人,就类似于我们日常所说的拖延症,火不烧到pp上就无动于衷的那种,下面给大家看看它的定义:
懒加载(Lazy Loading)是一种网页优化技术,主要目的是在不需要立即显示某些内容时,延迟加载这些内容,直到用户需要查看它们为止。
用大白话说就是我们在向上滚动页面时,如果下面还有图片的话,只有当这张图片的顶部在我们当前页面露出来的时候才会对该图片进行加载,下面给大家展示一下效果:
大家可以看到当我们的页面向下滚动时,图片只有当其露出一部分的时候再进行的加载,下面我们来跟大家聊聊如何实现懒加载。
1.1 懒加载的原理
懒加载原理:当页面加载完毕后,先判断在可视区域内的图片先加载,当用户滚动页面时,判断图片是否进入可视区。如果进入可视区,则将图片的src替换为图片的真实路径,从而实现图片的懒加载
1.2 懒加载的实现
我们在看完了懒加载的原理之后,接下来我们顺着懒加载原理的思路来用js实现一下这个效果。
首先我们在页面中放置几个img标签并且为每个img标签赋予属性data-src
和src
:
html
<body>
<img src="" data-src="https://t7.baidu.com/it/u=1732966997,2981886582&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=1785207335,3397162108&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=2581522032,2615939966&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=245883932,1750720125&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=3423293041,3900166648&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=3241434606,2550606435&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=1417505637,1247476664&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=3659156856,3928250034&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=1416385889,2308474651&fm=193&f=GIF" alt="">
<img src="" data-src="https://t7.baidu.com/it/u=2469680087,3014121106&fm=193&f=GIF" alt="">
</body>
由于这些标签中的src如果赋予值的话,那么当对img标签进行加载的时候就会发送http请求,所以这时候我们先不对其进行赋值,而是把值赋予给自定义属性data-src
,在需要展示该图片的时候就将data-src
中的值赋予src即可,下面我们来看看js的实现。
我们实现懒加载功能主要放在js中的lazyLoad()
函数中,在这个函数中我们先获取所有的img并用imgs来储存,由于获取到的是个数组,所以我们对imgs进行遍历,在这个遍历过程中我们就需要对每张图片进行判断,来判断它是否该出现在页面中,这时候就需要调用一个方法getBoundingClientRect()
,这个方法有什么用呢,下面我们来浏览器中打印看看:
我们可以看到这个方法可以用来获取当前图片在页面中的位置信息,而根据其中的top以及buttom来判断页面中是否应该出现该图片
- top: 用来获取图片顶部距离屏幕顶部的距离
- buttom: 用来获取图片底部距离屏幕顶部的距离
js
if (rect.top < height && rect.bottom > 0)
如果该图片顶部距离屏幕顶部小于用户屏幕高度并且底部距离距离顶部要大于0,就代表该图片应该展现在屏幕上,此时我们先新建一个Img标签 ,将原来img的data-src
的属性赋予给它,让它去进行http请求,然后对其进行监听将返回的请求结果返回给原来的img.src
,并且移除data-src
:
js
if (rect.top < height && rect.bottom > 0) {
let newImg = new Image() // 创建一个新的img标签
newImg.src = imgs[i].getAttribute('data-src') // getAttribute()获取自定义属性
newImg.onload = function () {
imgs[i].src = newImg.getAttribute('src')
} // 监听图片被浏览器加载完毕
imgs[i].removeAttribute('data-src') // 已经加载完就移除data-src属性
}
在这里可能就有同学会问了,我直接用img.src = imgs[i].getAttribute('data-src')
不就行了吗,为什么要多此一举呢?在这里呢我们用这个方法是将浏览器加载的压力分摊到了js上面,可以提高一点性能,当然用这种方法也是可以的,差别也没多大。接下来呢我们对页面滚动进行监听,每次滚动都调用一次这个lazyLoad()
即可:
js
lazyLoad()
window.addEventListener('scroll', lazyLoad) // 监听滚动事件,浏览器每次滚动屏幕就触发lazyLoad函数
下面是完整代码:
js
<script>
let height = window.innerHeight // 获取用户屏幕高度
// 判断默认情况下应该展示哪些图片
function lazyLoad() {
let imgs = document.querySelectorAll('img[data-src]') // 获取所有拥有data-src属性的img标签
for (let i = 0; i < imgs.length; i++) {
let rect = imgs[i].getBoundingClientRect() // 获取元素的几何属性
if (rect.top < height && rect.bottom > 0) {
let newImg = new Image() // 创建一个新的img标签
newImg.src = imgs[i].getAttribute('data-src') // getAttribute()获取自定义属性
newImg.onload = function () {
imgs[i].src = newImg.getAttribute('src')
} // 监听图片被浏览器加载完毕
imgs[i].removeAttribute('data-src') // 已经加载完就移除data-src属性
}
}
}
lazyLoad()
window.addEventListener('scroll', lazyLoad) // 监听滚动事件,浏览器每次滚动屏幕就触发lazyLoad函数
</script>
1.3 懒加载的缺点
这种实现懒加载的方式虽然看起来好像没啥问题,但是上帝为你打开一扇窗必定给你关了一扇门,它也有个缺点就是如果图片过大的话,那么请求需要时间吧,这时候已经加载好了img标签,则会在页面中出现短暂的白屏,这个是不可避免的。
缺点:当图片过大,图片出现在可视区域内的那一刻,也会有短暂的白屏,影响用户体验(万一图片过大,则会过一段时间出现)
2. 预加载
在了解完了懒大王之后我们来聊聊它的另一个比较勤奋的兄弟预加载,正所谓勤能补拙,我们不在需要它的时候再加载而是在开始的时候直接全部给它加载完不就好了,正所谓笨鸟先飞,预加载就是秉持着这个思想的。
2.1 预加载原理
预加载原理:在加载页面的同时,用第二个线程加载图片,当图片加载完毕后,将图片资源交给第一个线程去展示,从而实现图片的预加载
2.2 预加载实现
接下来呢我们同样用js来实现一下预加载,在这里呢有些同学可能会有点懵逼了,js不是单线程吗,怎么还整出第二个线程了?
这就不得不提了,虽然js默认是单线程的,但是官方在新版本中为我们提供了新的方法可以手动开辟一个新的线程供我们使用。接下来我们还是那几张图片的地址,我们在body中只有一个<div id="pic"></div>
我们会依次往其中插入img标签。我们用js来实现这个效果,首先我们定义一个数组arr
用来存放图片地址,然后调用new Worker()
来开辟一个新的线程程并且将数据arr
发送给这个子线程worker.postMessage(arr)
:
js
<script>
let pic = document.getElementById('pic')
let arr = [
"https://t7.baidu.com/it/u=1732966997,2981886582&fm=193&f=GIF",
"https://t7.baidu.com/it/u=1785207335,3397162108&fm=193&f=GIF",
"https://t7.baidu.com/it/u=2581522032,2615939966&fm=193&f=GIF",
"https://t7.baidu.com/it/u=245883932,1750720125&fm=193&f=GIF",
"https://t7.baidu.com/it/u=3423293041,3900166648&fm=193&f=GIF",
"https://t7.baidu.com/it/u=3241434606,2550606435&fm=193&f=GIF",
"https://t7.baidu.com/it/u=1417505637,1247476664&fm=193&f=GIF",
"https://t7.baidu.com/it/u=3659156856,3928250034&fm=193&f=GIF",
"https://t7.baidu.com/it/u=1416385889,2308474651&fm=193&f=GIF",
"https://t7.baidu.com/it/u=2469680087,3014121106&fm=193&f=GIF",
]
const worker = new Worker('worker.js') // 创建一个新的 worker 线程
worker.postMessage(arr) // 将数据发送给子线程(就是第二个线程)
</script>
接下来我们创建一个worker.js
文件在子线程来实现这个获取图片的功能,在worker.js中我们需要监听一下主线程传过来的信息,然后在这个事件中我们对传过来的数组进行遍历,将获取到的图片信息发送给主线程,下面给大家展示一下worker.js中的代码部分,每部分代码后面都有注释:
js
// 监听一下接收到的主线程的message
self.onmessage = function (e) {
// 将数组中的地址资源加载出来
let arr = e.data;
for (let i = 0; i < arr.length; i++) {
// 用Ajax请求
let xhr = new XMLHttpRequest()
xhr.open('GET', arr[i], true) // 返回图片的文件,异步的导致每次出现图片位置可能不同
xhr.responseType = 'blob' // 将文件转为blob类型
xhr.send()
xhr.onload = function () {
// 判断是否获取成功
if (xhr.readyState === 4 && xhr.status === 200) {
self.postMessage(xhr.response) // 将blob类型的图片数据发送给主进程
}
}
}
}
在上面这段代码中有很多需要注意的地方,下面为大家一一进行讲解:
只有主线程 可以用new Image()构造函数,子线程没有。所以不能在worker.js中使用这种方法为图片src赋值
blob类型是一种文件类型,所有类型都能转为blob类型
我们在了解完了这几点之后可能还有同学会对self
有点疑惑为什么这样写,这个不需要疑惑,接受就是了官方内定的,有句话不是说既然反抗不了那不就享受嘛。
接下来呢我们只需要在主线程中接收子线程返回回来的数据并对其进行监听即可,子线程每次返回一条数据就在div中使用appendChild
插入一张图片:
js
// 接收从子进程返回的数据,子进程每发一条就执行一次
worker.onmessage = function (e) {
const img = new Image()
img.src = window.URL.createObjectURL(e.data)
pic.appendChild(img)// 将img放入div中
}
</script>
在这段代码中有一个window.URL.createObjectURL()
这个方法,它的作用是:可以把对象转化为url,将本地的资源处理成本地的地址,如果别人想盗用你的图片则盗用不了是种反反爬手段 接下来给大家展示一下主进程html中的完整代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="pic"></div>
<script>
let pic = document.getElementById('pic')
let arr = [
"https://t7.baidu.com/it/u=1732966997,2981886582&fm=193&f=GIF",
"https://t7.baidu.com/it/u=1785207335,3397162108&fm=193&f=GIF",
"https://t7.baidu.com/it/u=2581522032,2615939966&fm=193&f=GIF",
"https://t7.baidu.com/it/u=245883932,1750720125&fm=193&f=GIF",
"https://t7.baidu.com/it/u=3423293041,3900166648&fm=193&f=GIF",
"https://t7.baidu.com/it/u=3241434606,2550606435&fm=193&f=GIF",
"https://t7.baidu.com/it/u=1417505637,1247476664&fm=193&f=GIF",
"https://t7.baidu.com/it/u=3659156856,3928250034&fm=193&f=GIF",
"https://t7.baidu.com/it/u=1416385889,2308474651&fm=193&f=GIF",
"https://t7.baidu.com/it/u=2469680087,3014121106&fm=193&f=GIF",
]
const worker = new Worker('worker.js') // 创建一个新的 worker 线程
worker.postMessage(arr) // 将数据发送给子线程(就是第二个线程)
// 接收从子进程返回的数据,子进程每发一条就执行一次
worker.onmessage = function (e) {
const img = new Image()
img.src = window.URL.createObjectURL(e.data)
pic.appendChild(img)// 将img放入div中
}
</script>
</body>
</html>
tips :之所以将文件转化为
blob
类型,是因为window.URL.createObjectURL()
这个方法可以将对象转化为URL,并且是本地地址,所以不同十六进制或者别的。
2.3 预加载的缺点
在前面我们说到懒加载会出现白屏是因为图片发送http请求需要时间,预加载同样也需要这样的话它的白屏时间就会集中出现在开始加载的时候,这时候我们可以在页面放个loading这种东西,等到加载完成后再展现出来。这玩意咱也避免不了。
缺点:同一时间加载多张图片,对服务器压力较大,并不会影响用户体验,开始可能会出现白屏一会儿
结语
懒加载和预加载,这两位技术界的'好兄弟',各有千秋,各有所长。懒加载是'懒人'的好帮手,等你需要的时候才会加载资源,节省了流量和时间,让用户体验更流畅。预加载则是'提前量'的高手,预先加载资源,给用户带来更快的访问速度和更好的体验,在实际开发中我们得根据实际情况灵活变换使用两种方式来对网页进行优化。
最后谢谢各位观众大佬!!!