共计 4070 个字符,预计需要花费 11 分钟才能阅读完成。
什么是图片懒加载?
现在一张图片的大小能够轻松达到几 M
的大小,如果一个页面中同时有比拟多这样的图片,同时加载的话会造成页面加载迟缓,当指标图片未加载胜利时,通常会应用一个小图片占位,例如一个转圈圈的 gif 来示意图片正处于 loading
状态,但这扭转不了很多图片同时加载带来的加载迟缓的问题。
因而很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载,这样对页面的加载性能有较大晋升,同时也改善了用户体验。
次要实现思路如下:
1、img
元素的自定义属性上 data-xxx
上挂载指标图片 url
, src
属性指向默认图片地址。
2、监听图片是否呈现在用户的可视区域内。
3、呈现在可视区域内后更新 src
属性为指标图片url
。
以下两种实现形式的区别次要体现在第二步。
监听 scroll 事件 + 节流 实现
<!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>Document</title>
</head>
<body>
<div></div>
</body>
<style>
div {
display: flex;
justify-content: center;
flex-direction: column;
}
.img {
width: 50%;
height: 50%;
}
</style>
<script>
const loadingSrc = 'https://c.tenor.com/j-sGh5ZtdaEAAAAi/hongfei-fei.gif'
const srcs = [
'https://w.wallhaven.cc/full/wq/wallhaven-wq855r.png',
'https://w.wallhaven.cc/full/6o/wallhaven-6ooo66.png',
'https://w.wallhaven.cc/full/eo/wallhaven-eoqk88.png',
'https://w.wallhaven.cc/full/8x/wallhaven-8xlx2o.png',
'https://w.wallhaven.cc/full/q2/wallhaven-q2drrl.jpg',
'https://w.wallhaven.cc/full/ym/wallhaven-ymrrex.png',
'https://w.wallhaven.cc/full/mp/wallhaven-mp8wkm.png',
'https://w.wallhaven.cc/full/dg/wallhaven-dgzp2m.jpg',
'https://w.wallhaven.cc/full/lq/wallhaven-lqek12.png',
'https://w.wallhaven.cc/full/r7/wallhaven-r7mkzq.jpg',
'https://w.wallhaven.cc/full/qd/wallhaven-qd12jl.png',
'https://w.wallhaven.cc/full/0q/wallhaven-0q23r5.jpg',
'https://w.wallhaven.cc/full/76/wallhaven-76wj9o.png',
'https://w.wallhaven.cc/full/5d/wallhaven-5d6gd5.jpg',
'https://w.wallhaven.cc/full/gj/wallhaven-gjmd9q.png',
'https://w.wallhaven.cc/full/pk/wallhaven-pk91m3.png',
'https://w.wallhaven.cc/full/pk/wallhaven-pk9zm9.png',
'https://w.wallhaven.cc/full/j8/wallhaven-j85wv5.png',
]
for (let src of srcs) {let imgNode = document.createElement('img')
document.body.appendChild(imgNode)
imgNode.src = loadingSrc
imgNode.className = 'img'
imgNode.setAttribute('data-src', src)
}
let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
</script>
</html>
渲染后的构造如下图所示,src
属性指向一张默认加载图片的地址,data-src
属性上是指标图片的地址
在此办法中懒加载的外围逻辑为:监听 scroll 事件,比照容器的高度、滚动高度、和图片间隔容器顶部的高度,判断是否滚动到可视区域
次要实现如下:
const imgLazyLoad = (function () {
let count = 0
return function () {let deleteIndexList = []
imgList.forEach((img, index) => {let rect = img.getBoundingClientRect()
if (rect.top < window.innerHeight) {console.log(` 加载第 ${index}张图片 `)
img.src = img.dataset.src
deleteIndexList.push(index)
count++
}
// 阐明图片曾经全副加载实现了,移除监听事件
if (count === length) {document.removeEventListener('scroll', imgLazyLoad)
}
})
// 剔除曾经实现加载的图片的地址
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index)
)
}
})()
// 监听鼠标滚动事件
document.addEventListener('scroll', imgLazyLoad)
Element.getBoundingClientRect() 返回元素的大小以及绝对于视口的地位.
window.innerheight 是视口的高度.
当img.top
<window.innerHeight
时阐明该图片元素正处于视口之内,应该进行加载,将img.src
的值更换为img.dataset.src
的值,加载指标图片。
让咱们来看一下应用成果:
能够看出,当鼠标滚动时,首先加载在以后视口内的图片,当新的图片进入视口内时,加载新的图片,所有图片并没有同时进行加载,达到了预期成果。
然而,间接将函数绑定在 scroll
事件上,当页面滚动时,函数会被高频触发。因而咱们再应用节流函数优化一下。
function throttle(func, wait) {
let prev = 0,
context,
args
return function () {let now = +new Date()
context = this
args = arguments
if (now - prev > wait) {func.apply(context, args)
prev = now
}
}
}
// 监听鼠标滚动事件
document.addEventListener('scroll', throttle(imgLazyLoad, 500))
在线地址
然而!!!
getBoundingClientRect
返回最新的地位信息,会触发回流重绘以返回正确值
与
getBoundingClientRect
一样会触发回流重绘的还有:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle() 具体请看这里
这里应用了节流缩小了回调函数的调用次数,但有没有方法不触发回流重绘也能检测到图片处于以后视口呢?这就是接下来要介绍的intersectionObserver
API
应用 intersectionObserver
API 实现
IntersectionObserver
是浏览器原生提供的构造函数,承受两个参数:callback 是可见性变动时的回调函数,option 是配置对象(该参数可选)。
callback 函数的参数(entries)是一个数组,每个成员都是一个 IntersectionObserverEntry 对象
其中:isIntersecting 示意指标是否可见,即是否在视口中
具体可见 MDN
function imgLazyLoad() {const io = new IntersectionObserver((inters) => {inters.forEach((item, index) => {
// 进入可视区域
if (item.isIntersecting) {
item.target.src = item.target.dataset.src
// 进行监听该元素
io.unobserve(item.target)
}
})
})
// 监听每一个元素
imgList.forEach((el) => io.observe(el))
}
相比于应用getBoundingClientRect
,实现了雷同的懒加载成果,但防止了不必要的回流重绘。
在线地址
参考
前端性能优化之图片懒加载
实现图片懒加载(Lazyload)
MDN – Intersection Observer