图片懒加载原理
本文最后更新于:2022年4月22日 上午
通过 getBoundingClientRect
和 IntersectionObserver
分别实现图片懒加载
明确目标
我们先写一个基本的页面,让 图片 超出窗口的高度,然后我们改写成当滚动到图片标签位置时再向服务器请求资源
<!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>
<style>
html,
body {
height: 3000px;
}
section {
width: 600px;
height: 300px;
margin: 1500px auto 0; // 让容器距离顶部有一段距离,需要滚动屏幕才能出现
background-color: #ccc;
}
section img {
width: 100%;
height: 100%;
opacity: 0; // 默认透明度为0
transition: opacity 1s;
}
</style>
</head>
<body>
<section>
<img src="" alt="" />
</section>
</body>
</html>
可以看到当前我们的布局,需要向下滚动一定的距离,才能到达 img
标签所在的位置
getBoundingClientRect方式实现
getBoundingClientRect
可以用来获取某个元素相对于窗口的 top
,left
,right
,bottom
值
getBoundingClientRect 的基本使用
const box = document.getElementById("box")
const { top, left, right, bottom } = box.getBoundingClientRect();
getBoundingClientRect + onscroll
一般我们都能想到使用 需要用到监听屏幕滚动,采用监听屏幕滚动的方式具体方案如下
获取 可视窗口的高度
监听浏览器滚动
滚动触发时获取
img
标签距离顶部的距离,当距离顶部的距离小于可视窗口高度时,说明现在 **img
标签已经进入了可视范围** ,可以开始正式加载图片了
如下图所示
新增自定义属性
先给 img
标签新增一个自定义属性 data-src
将我们真实需要加载的图片地址放在 data-src
上
<img
src=""
alt=""
data-src="https://img1.baidu.com/it/u=588478138,900370032&fm=26&fmt=auto&gp=0.jpg"
>
获取到 可视窗口高度 和 img
标签
const img = document.querySelector('img');
const clientHeight = document.documentElement.clientHeight; // 获取窗口可视区域高度
封装 图片懒加载 函数
给 img
添加一个 isLoad
用来标识图片是否已经加载过,
- 加载过
    如果加载过则 return
- 未加载过
    - 从 data-src
取到图片地址赋值给src
属性,
    - 监听图片onload
事件,触发后将opacity
设置为 1
,并且将 isLoad
标识改为 true
    
function lazyImgLoad() {
if (img.isLoad) return;
const src = img.getAttribute('data-src');
img.src = src;
img.onload = () => {
img.style.opacity = 1;
img.isLoad = true;
}
}
监听 屏幕滚动
window.onscroll = function () {
const { top } = img.getBoundingClientRect(); // 获取元素的位置信息
// 判断距离顶部的位置是否小于可视窗口区域,如果小于则调用 懒加载函数
if (top <= clientHeight) lazyImgLoad();
}
基本实现
现在我们的懒加载已经基本实现了,打开浏览器的 network 可以看到,当我们第一次进入页面的时候,是不会请求图片的,当滚动到图片区域的时候,才开始发送请求
优化
我们会发现,我们上面的代码,每当我们滚动的时候,都会触发 onscroll
监听函数,浏览器会在最快的时间触发监听函数,一般为 5ms - 7ms
之间,这个频率很明显太快了。
这里我们可以用节流来避免短时间内频繁触发来优化一下性能
function throttle(fn, delay = 500) {
let time;
return (...args) => {
if (time) return; // 节流
time = setTimeout(() => {
time = null; // 节流
fn.apply(null, args)
}, delay);
}
}
window.onscroll = throttle(function (e) {
const { top } = img.getBoundingClientRect(); // 获取元素的位置信息
if (top <= clientHeight) lazyImgLoad();
},100)
IntersectionObserver 方式实现
虽然上述使用 节流来优化了一下性能,但是也牺牲快速响应,所以这里我们可以来使用浏览器自带的 IntersectionObserver
,它可以用来异步观察元素(支持多个)是否在可视窗口内,这个 api
除了 IE
,其它浏览器都兼容。
IntersectionObserver 基本使用
语法也比较简单,第一个参数是回调函数,当元素与窗口交叉的时候会触发,第二个参数是 options
参数,这个参数非必传
这个函数会返回一个观察器的实例,可以通过它来操作需要观察哪个 dom
元素
const observe = IntersectionObserver(callback, options)
创建监听对象
const ob = new IntersectionObserver(entries => {
console.log(entries[0]);
}, {
threshold: [1], // 表示完全出现时会触发一次回调
});
// entrie 对象结构如下
{
boundingClientRect: DOMRectReadOnly {x: 101, y: 479, width: 600, height: 300, top: 479, …}
intersectionRatio: 1
intersectionRect: DOMRectReadOnly {x: 101, y: 479, width: 600, height: 300, top: 479, …}
isIntersecting: true // 表示容器是否当前是否可见
isVisible: false
rootBounds: DOMRectReadOnly {x: 0, y: 0, width: 802, height: 782, top: 0, …}
target: img
time: 1428.300000011921
}
添加回调处理函数
const ob = new IntersectionObserver(entries => {
const { isIntersecting, target } = entries[0];
if (isIntersecting) { // 当处于可见时,开始加载图片,且停止监听当前目标
lazyImgLoad();
ob.unobserve(target);
}
}, {
threshold: [1],
});
监听容器
ob.observe(img);
实现
可以看到,完美实现,且不需要手动做其它优化,目前基本上的ui库图片懒加载的原理都与此类似
最后附上完整代码
<!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>
<style>
html,
body {
height: 3000px;
}
section {
width: 600px;
height: 400px;
margin: 1500px auto 0;
background-color: #ccc;
}
section img {
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 1s;
}
</style>
</head>
<body>
<section>
<img src="" alt=""
data-src="https://img1.baidu.com/it/u=588478138,900370032&fm=26&fmt=auto&gp=0.jpg">
</section>
<script>
// 图片懒加载是当图片进入可视区域的时候才开始加载
// 监听图片顶部距离窗口顶部的距离,如果图片顶部具体窗口顶部的距离小于 窗口的高度,则表示,图片进入了可视区域
const img = document.querySelector('img');
const clientHeight = document.documentElement.clientHeight; // 获取窗口可视区域高度
function lazyImgLoad() {
if (img.isLoad) return;
const src = img.getAttribute('data-src');
img.src = src;
img.onload = () => {
img.style.opacity = 1;
img.isLoad = true;
}
}
// 使用 onscroll 实现
// window.onscroll = throttle(function (e) {
// const { top } = img.getBoundingClientRect(); // 获取元素的位置信息
// if (top <= clientHeight) lazyImgLoad();
// })
function throttle(fn, delay = 500) {
let time;
return (...args) => {
// if (time) clearTimeout(time); // 防抖
if (time) return; // 节流
time = setTimeout(() => {
time = null; // 节流
fn.apply(null, args)
}, delay);
}
}
// 优化
// 通过浏览器的 IntersectionObserver 来帮助我们监听,加强性能
// 但是兼容 `IE`, 需要兼容 IE 的尽早逃吧,不要把有限的时间浪费在无意义的事情上。
// 创建一个监听对象
const ob = new IntersectionObserver(change => {
console.log(change[0]);
/*
boundingClientRect: DOMRectReadOnly {x: 101, y: 479, width: 600, height: 300, top: 479, …}
intersectionRatio: 1
intersectionRect: DOMRectReadOnly {x: 101, y: 479, width: 600, height: 300, top: 479, …}
isIntersecting: true // 表示容器是否当前是否可见
isVisible: false
rootBounds: DOMRectReadOnly {x: 0, y: 0, width: 802, height: 782, top: 0, …}
target: img
time: 1428.300000011921
*/
const { isIntersecting, target } = change[0];
if (isIntersecting) {
lazyImgLoad();
ob.unobserve(target);
}
}, {
threshold: [1], // 表示完全出现时会触发一次回调
});
// 调用 observe 方法,将 img 传入,表示监听 img 元素
ob.observe(img);
// console.log(ob);
</script>
</body>
</html>
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。