本文最后更新于:8 个月前
图片懒加载,关键在于如何判断图片是否出现在可视范围,本文介绍了两种实现方法:第一种,window.onscroll监听事件+节流+坐标对比;第二种,使用Intersection Observer接口
性能优化——图片懒加载
图片懒加载,简单来说,就是还没有出现在用户可见范围内的图像资源不加载
一、实现原理
<img>
标签本身有一个loading
属性来决定是否执行懒加载,但是实测中发现该属性在Edge浏览器中并不起作用,所以需要通过脚本的方式去实现。
懒加载的实现方式,通过判断<img>
标签所处的坐标是否出现在了网页视口,将<img>
标签的src
值替换为存放在data-src
属性里的真正图像链接
判断<img>
标签是否出现在可视范围有两种实现方式:
- 监听
window.onscroll
事件,在回调函数中去对比每个<img>
标签的坐标和窗口尺寸
- 使用Intersection Observer这个接口,判断
<img>
标签是否出现在可视范围
二、前置知识
1、浏览器屏幕坐标系
![image-20221103101645081](https://qiuzcc-typora-images.oss-cn-shenzhen.aliyuncs.com/images/2022/202211031016141.png)
存在两种坐标系:
- 相对于窗口window,从窗口的(顶部,左部)开始计算,坐标变量为**
clientX
和clinetY
**
- 相对于文档document,从文档根的(顶部,左部)开始,坐标变量为**
pageX
和pageY
**
关系:pageX = clientX + scrollLeft
,pageY = clientY + scrollTop
其中scrollTop/scrollLeft
代表页面节点与文档根(顶部,左部)的滚动距离,
1 2 3 4
| window.pageYOffset document.body.scrollTop document.documentElement.scrollTop
|
2、getBoundingClientRect()
该方法返回DOM节点的矩形盒子的属性对象,该矩形将 elem
作为内建 DOMRect 类的对象。这个对象有许多属性,
1 2 3 4 5 6 7 8 9 10
| DOMRect { x: 236.5, y: 900, width: 300, height: 300, top: 900, bottom: 1200, left: 236.5, right: 536.5, }
|
当 y
属性的值 小于 window.innerHeight
(可视窗口的高度)时,意味着元素进入了可视范围内
3、获取窗口尺寸
窗口有两种定义,一种是当前窗口的实际大小(可能因为被缩小而变化),另外一种是屏幕的大小(固定值)
第一种,获取当前窗口实际大小,使用window对象
1 2 3 4 5 6 7 8 9
| window.innerHeight document.body.clientHeight document.documentElement.clientHeight
window.innerWidth document.body.clientWidth document.documentElement.clientWidth
|
第二种,获取屏幕固定宽高
1 2 3 4 5
| screen.width
screen.height
|
4、Intersection Observer
该接口提供了一种异步观察目标元素与其祖先元素或顶级文档视窗 (viewport) 交叉状态的方法
该接口可以方便的检测元素的可视状态,应用场景广泛:
- 当页面滚动时,懒加载图片或其他内容。
- 实现“可无限滚动”网站,也就是当用户滚动网页时直接加载更多内容,无需翻页。
- 对某些元素进行埋点曝光
- 滚动到相应区域来执行相应动画或其他任务。
构造器
IntersectionObserver()
,接受一个回调函数
1 2 3 4 5
| const observer = new IntersectionObserver((entries)=>{ console.log(entries) }) let hList = document.querySelectorAll('h3') observer.observe(hList[0])
|
输出结果如下:
![image-20221103130154181](https://qiuzcc-typora-images.oss-cn-shenzhen.aliyuncs.com/images/2022/202211031301216.png)
可以看到enries
是一个IntersectionObserverEntry
对象数组,该对象的属性如上,其中isIntersecting
是一个Boolean值,表示被观察的节点是否与文档视窗交叉(即有没有出现在可见窗口中)
用法示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <!DOCTYPE html> <html lang="en"> <head> <title>Document</title> <style> h3 { margin: 200px; } </style> </head> <body> <div> <h3>测试文本1</h3> <h3>测试文本2</h3> <h3>测试文本3</h3> <h3>测试文本4</h3> <h3>测试文本5</h3> </div> <script> const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { let node = entry.target console.log(node.textContent + ' 出现在了窗口中') observer.unobserve(node) } }) }) let hList = document.querySelectorAll('h3') hList.forEach(h => { observer.observe(h) }) </script> </body> </html>
|
![image-20221103130952810](https://qiuzcc-typora-images.oss-cn-shenzhen.aliyuncs.com/images/2022/202211031309882.png)
三、动手实践
第一种方式:监听window.onscroll
事件,在回调中判断图片是否出现在窗口可视范围,使用节流优化监听事件
完整文件源码:study/img_lazy_load_1.html at master · Qiuzcc/study · GitHub
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <!DOCTYPE html> <html lang="en"> <body> <img src="https://qiuzcc-typora-images.oss-cn-shenzhen.aliyuncs.com/images/empty_box-Large.jpg" data-src="https://qiuzcc-typora-images.oss-cn-shenzhen.aliyuncs.com/images/IMG_3995.JPG" alt=""> <script> function throttle(fn, interval){...} let imgs = document.getElementsByTagName('img') let curLoadImgIndex = 0 function imgLazyLoad() { for (let i = curLoadImgIndex; i < imgs.length; i++) { let img = imgs[i] if (img.getBoundingClientRect().top <= window.innerHeight) { img.src = img.dataset.src img.dataset.src = null curLoadImgIndex = i + 1 } } } window.onscroll = throttle(imgLazyLoad, 250)
imgLazyLoad() </script> </body> </html>
|
2.方式二:IntersectionObserver
完整文件代码:study/img_lazy_load_2.html at master · Qiuzcc/study · GitHub
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script> const imgs = Array.from(document.getElementsByTagName('img')) const imgLazyLoad = (imgs) => { imgs.forEach(item => { if (item.isIntersecting) { const img = item.target img.src = img.dataset.src img.dataset.src = null observer.unobserve(img) } }) } const observer = new IntersectionObserver(imgLazyLoad) imgs.forEach(img => { observer.observe(img) }) </script>
|
IntersectionObserver
允许你追踪目标元素与其祖先元素或视窗的交叉状态
通过isIntersecting
属性来判断是否出现
observer.observe
添加交叉监听,给每个img添加监听
observer.unobserve
取消交叉监听,img出现时给src赋值,并取消监听
参考链接
【译】浏览器的坐标系统 · YAPN (gitbooks.io)
坐标 (javascript.info)
Intersection Observer - Web API 接口参考 | MDN (mozilla.org)