网站开发中,如何实现图片的懒加载
更多描述 网站开发中,如何实现图片的懒加载,随着 web 技术的发展,他有没有一些更好的方案
Issue 欢迎在 Gtihub Issue 中回答此问题: Issue 1
Author 回答者: shfshanyue
懒加载,顾名思义,在当前网页,滑动页面到能看到图片的时候再加载图片
故问题拆分成两个:
- 如何判断图片出现在了当前视口 (即如何判断我们能够看到图片)
- 如何控制图片的加载
方案一: 位置计算 + 滚动事件 (Scroll) + DataSet API
如何判断图片出现在了当前视口
clientTop
,offsetTop
,clientHeight
以及 scrollTop
各种关于图片的高度作比对
这些高度都代表了什么意思?
这我以前有可能是知道的,那时候我比较单纯,喜欢死磕。我现在想通了,背不过的东西就不要背了
所以它有一个问题:复杂琐碎不好理解!
仅仅知道它静态的高度还不够,我们还需要知道动态的
如何动态?监听 window.scroll
事件
如何控制图片的加载
<img data-src="shanyue.jpg" />
首先设置一个临时 Data 属性 data-src
,控制加载时使用 src
代替 data-src
,可利用 DataSet API 实现
img.src = img.datset.src
方案二: getBoundingClientRect API + Scroll with Throttle + DataSet API
改进一下
如何判断图片出现在了当前视口
引入一个新的 API, Element.getBoundingClientRect()
方法返回元素的大小及其相对于视口的位置。
那如何判断图片出现在了当前视口呢,根据示例图示意,代码如下,这个就比较好理解了,就可以很容易地背会(就可以愉快地去面试了)。
// clientHeight 代表当前视口的高度
img.getBoundingClientRect().top < document.documentElement.clientHeight;
监听 window.scroll
事件也优化一下
加个节流器,提高性能。工作中一般使用 lodash.throttle
就可以了,万能的 lodash
啊!
_.throttle(func, [(wait = 0)], [(options = {})]);
参考 什么是防抖和节流,他们的应用场景有哪些,或者前端面试题
方案三: IntersectionObserver API + DataSet API
再改进一下
如何判断图片出现在了当前视口
方案二使用的方法是: window.scroll
监听 Element.getBoundingClientRect()
并使用 _.throttle
节流
一系列组合动作太复杂了,于是浏览器出了一个三合一事件: IntersectionObserver
API,一个能够监听元素是否到了当前视口的事件,一步到位!
事件回调的参数是 IntersectionObserverEntry 的集合,代表关于是否在可见视口的一系列值
其中,entry.isIntersecting
代表目标元素可见
const observer = new IntersectionObserver((changes) => {
// changes: 目标元素集合
changes.forEach((change) => {
// intersectionRatio
if (change.isIntersecting) {
const img = change.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
observer.observe(img);
当然,IntersectionObserver
除了给图片做懒加载外,还可以对单页应用资源做预加载。
如在 next.js v9
中,会对视口内的资源做预加载,可以参考 next 9 production optimizations
<Link href="/about">
<a>关于山月</a>
</Link>
方案四: LazyLoading属性
浏览器觉得懒加载这事可以交给自己做,你们开发者加个属性就好了。实在是…!
<img src="shanyue.jpg" loading="lazy" />
不过目前浏览器兼容性不太好,关于 loading
属性的文章也可以查看 Native image lazy-loading for the web!
Author 回答者: hanhang123
intersectionObserver
Author 回答者: AgnesWY
比较单纯,喜欢死磕。我现在想通了,背不过的东西就不要背了!!!
Author 回答者: Kiera569
那时候我比较单纯,喜欢死磕。我现在想通了,背不过的东西就不要背了
Author 回答者: haiifeng
那时候我比较单纯,喜欢死磕。我现在想通了,背不过的东西就不要背了
Author 回答者: hwb2017
方案二的简单Demo:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片懒加载</title>
<style>
img {
width: 100%;
height: 600px;
}
</style>
</head>
<body>
<img
src="https://cdn.pixabay.com/photo/2021/08/24/15/38/sand-6570980_960_720.jpg"
alt="1"
/>
<img
src="https://cdn.pixabay.com/photo/2013/02/21/19/06/drink-84533_960_720.jpg"
alt="2"
/>
<img
data-src="https://cdn.pixabay.com/photo/2014/12/15/17/16/boardwalk-569314_960_720.jpg"
alt="3"
/>
<img
data-src="https://cdn.pixabay.com/photo/2013/07/18/20/26/sea-164989_960_720.jpg"
alt="4"
/>
<img
data-src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg"
alt="5"
/>
<img
data-src="https://cdn.pixabay.com/photo/2017/03/26/21/54/yoga-2176668_960_720.jpg"
alt="6"
/>
<img
data-src="https://cdn.pixabay.com/photo/2015/03/17/14/05/sparkler-677774_960_720.jpg"
alt="7"
/>
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js"></script>
<script>
const images = document.querySelectorAll("img");
const lazyLoad = () => {
images.forEach((item) => {
// 触发条件为img元素的CSSOM对象到视口顶部的距离 < 100px + 视口高度,+100px为了提前触发图片加载
if (
item.getBoundingClientRect().top <
document.documentElement.clientHeight + 100
) {
if ("src" in item.dataset) {
item.src = item.dataset.src;
}
}
});
};
document.addEventListener("scroll", _.throttle(lazyLoad, 200));
</script>
</body>
</html>
Author 回答者: shfshanyue
@hwb2017 可以在 codepen 里写一下,然后附个地址
Author 回答者: hwb2017
方案二的Demo(CodePen) https://codepen.io/hwb2017/pen/BaZKeLa
Author 回答者: Ha0ran2001
在react hook中要怎么应用?看到这篇文章https://juejin.cn/post/6844903768966856717,但是改成 useRef 不行,hook 不能在循环中使用
Author 回答者: liucan233
方案一的实现demo,ScrollListener类用于监听和处理滚动,在Controller(实现onEnterViewport方法)元素出现在视窗内时调用controller.onEnterViewport(),最后移除controller。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>offsetTop计算实现图片懒加载</title>
<style>
body {
margin: 0;
}
.img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.wrap {
margin: 10px;
display: inline-block;
width: 480px;
height: 270px;
}
.container {
width: 100vw;
height: 100vh;
overflow: auto;
}
h1 {
text-align: center;
}
.main {
margin: 0;
width: 2000px;
}
</style>
</head>
<body>
<section class="container">
<h1>请滚动页面查看效果</h1>
<div class="main"></div>
</section>
</body>
<script defer>
"use strict";
// 图片url列表
const images = [
"https://h2.ioliu.cn/bing/Latern2022_ZH-CN0112710917_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/MaldivesHeart_ZH-CN0032539727_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/FaceOff_ZH-CN9969100257_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/DarwinsArch_ZH-CN9740478501_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/TeaGardensMunnar_ZH-CN9587720369_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/SnowyBern_ZH-CN5472524801_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/SevenSistersCliffs_ZH-CN5362127173_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/SpeloncatoSnow_ZH-CN8115437163_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/WinterludeIce_ZH-CN7868524911_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/Oymyakon_ZH-CN7758768574_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/MexicoMonarchs_ZH-CN7526758236_640x480.jpg?imageslim",
"https://h2.ioliu.cn/bing/WinterOlymics_ZH-CN7384614076_640x480.jpg?imageslim",
"233",
];
// 未加载时默认url
const defaultUrl =
"data:image/svg+xml;base64,PHN2ZyB0PSIxNjQ0ODk5MzI0NDgwIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjIwOTMiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNODc0LjEgODEzLjc1SDE0OS45Yy0yMi4yMiAwLTQwLjIzLTE4LjAxLTQwLjIzLTQwLjIzVjI1MC40OWMwLTIyLjIyIDE4LjAxLTQwLjIzIDQwLjIzLTQwLjIzaDcyNC4yYzIyLjIyIDAgNDAuMjMgMTguMDEgNDAuMjMgNDAuMjN2NTIzLjAzYzAgMjIuMjEtMTguMDIgNDAuMjMtNDAuMjMgNDAuMjN6TTI4MC42NiAzMTAuODRjLTM4Ljg5IDAtNzAuNDEgMzEuNTItNzAuNDEgNzAuNDFzMzEuNTIgNzAuNDEgNzAuNDEgNzAuNDEgNzAuNDEtMzEuNTIgNzAuNDEtNzAuNDEtMzEuNTItNzAuNDEtNzAuNDEtNzAuNDF6IG01MTIuOTcgMTAwLjU4YzAtMjIuMjItMTguMDEtNDAuMjMtNDAuMjMtNDAuMjNoLTQwLjIzYy02Ni42NiAwLTEyMC43IDU0LjA0LTEyMC43IDEyMC43djQwLjIzYzAgMzMuMzMtMjcuMDIgNjAuMzUtNjAuMzUgNjAuMzUtMTguMjkgMC0zNC40Ny04LjMxLTQ1LjU0LTIxLjE1LTAuMDUtMC4wNi0wLjI1LTAuMjgtMC4yOS0wLjMzLTIyLjA5LTI0LjA1LTU5Ljc3LTM4Ljg2LTk0Ljk4LTM4Ljg2LTAuNDQgMC0wLjg0IDAuMTItMS4yOCAwLjEzbC0wLjA2LTAuMDZjLTg4LjI2IDAuNzMtMTU5LjU5IDcyLjQ0LTE1OS41OSAxNjAuODYgMCAyMi4yMiAxOC4wMSA0MC4yMyA0MC4yMyA0MC4yM0g3NTMuNGMyMi4yMiAwIDQwLjIzLTE4LjAxIDQwLjIzLTQwLjIzVjQxMS40MnoiIHAtaWQ9IjIwOTQiIGZpbGw9IiNjZGNkY2QiPjwvcGF0aD48L3N2Zz4=";
// 加载错误时代替
const errorUrl =
"data:image/svg+xml;base64,PHN2ZyB0PSIxNjQ0ODk5ODEzMDQ1IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQ0OTEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNjQuMzgzMjM0IDUxMkM2NC4zODMyMzQgMjY0Ljc4NzgyOSAyNjQuNzg3ODI5IDY0LjM4MzIzNCA1MTIgNjQuMzgzMjM0IDc1OS4yMTIxNzEgNjQuMzgzMjM0IDk1OS42MTY3NjYgMjY0Ljc4NzgyOSA5NTkuNjE2NzY2IDUxMiA5NTkuNjE2NzY2IDc1OS4yMTIxNzEgNzU5LjIxMjE3MSA5NTkuNjE2NzY2IDUxMiA5NTkuNjE2NzY2IDI2NC43ODc4MjkgOTU5LjYxNjc2NiA2NC4zODMyMzQgNzU5LjIxMjE3MSA2NC4zODMyMzQgNTEyWk00NzQuMjMyMzc5IDc3MS4yNDUxMjRDNDc2LjQwODcxOCA3OTcuMzU1NTEyIDQ5MC41NTEzNzIgODEwLjQxMjEyMyA1MTYuNjYzMTc2IDgxMC40MTIxMjMgNTQyLjc3MzU2NCA4MTAuNDEyMTIzIDU1Ni45MTc2MzUgNzk3LjM1NTUxMiA1NTkuMDkyNTU2IDc3MS4yNDUxMjQgNTU2LjkxNzYzNSA3NDUuMTMzMzE5IDU0Mi43NzM1NjQgNzMwLjk5MDY2NiA1MTYuNjYzMTc2IDcyOC44MTQzMjcgNDkwLjU1MTM3MiA3MzAuOTkwNjY2IDQ3Ni40MDg3MTggNzQ1LjEzMzMxOSA0NzQuMjMyMzc5IDc3MS4yNDUxMjRaTTQ4MC43NTk5NzcgNjExLjMxNDc0OEM0ODAuNzU5OTc3IDYzNy40MjY1NTQgNDkyLjcyNzcxIDY1MC40ODE3NDcgNTE2LjY2MzE3NiA2NTAuNDgxNzQ3IDU0MC41OTcyMjYgNjUwLjQ4MTc0NyA1NTIuNTY0OTYgNjM3LjQyNjU1NCA1NTIuNTY0OTYgNjExLjMxNDc0OEw1NTIuNTY0OTYgMjQ5LjAyNDYxOEM1NTIuNTY0OTYgMjIyLjkxNDIzMSA1NDAuNTk3MjI2IDIwOS44NTc2MTkgNTE2LjY2MzE3NiAyMDkuODU3NjE5IDQ5Mi43Mjc3MSAyMDkuODU3NjE5IDQ4MC43NTk5NzcgMjIyLjkxNDIzMSA0ODAuNzU5OTc3IDI0OS4wMjQ2MThMNDgwLjc1OTk3NyA2MTEuMzE0NzQ4WiIgcC1pZD0iNDQ5MiIgZmlsbD0iI2NkY2RjZCI+PC9wYXRoPjwvc3ZnPg==";
// 滚动监听和防抖
class ScrollListener {
entries = [];
taskId = 0;
constructor() {
document.addEventListener("scroll", this.scrollDebounce.bind(this), {
capture: true,
passive: true,
});
}
isInViewport(controller) {
let offsetTop = 0,
offsetLeft = 0,
el = controller.el,
scrollTop = 0,
scrollLeft = 0,
html = document.documentElement;
while (el && el !== html) {
offsetTop = offsetTop + el.offsetTop;
offsetLeft = offsetLeft + el.offsetLeft;
el = el.offsetParent;
}
el = controller.el;
while (el) {
scrollTop += el.scrollTop;
scrollLeft += el.scrollLeft;
el = el.parentElement;
}
offsetTop -= scrollTop;
offsetLeft -= scrollLeft;
el = controller.el;
return (
offsetTop < html.scrollTop + innerHeight &&
offsetTop + el.clientHeight > html.scrollTop &&
offsetLeft < html.scrollLeft + innerWidth &&
offsetLeft + el.clientWidth > html.scrollLeft
);
}
scrollDebounce() {
if (this.taskId) {
clearTimeout(this.taskId);
}
this.taskId = setTimeout(this.handleScroll.bind(this), 200);
}
addController(controller) {
this.entries.push(controller);
this.scrollDebounce();
}
handleScroll() {
this.entries = this.entries.filter((controller) => {
return !controller.blob;
});
this.entries.forEach((controller) => {
if (this.isInViewport(controller)) {
controller.onEnterViewport();
}
});
}
}
// 图片控制对象
class ImageController {
img = "";
blob = null;
el = null;
wrap = null;
constructor(
url = "",
parent = document.body,
className = "wrap",
el = document.createElement("img"),
) {
el.src = defaultUrl;
el.classList.add("img");
this.el = el;
this.img = url;
this.wrap = document.createElement("div");
this.wrap.classList.add(className);
this.wrap.append(el);
parent.append(this.wrap);
}
showImage() {
const target = this;
this.fetchImage().then(() => {
target.el.src = this.blob;
});
}
showLoading() {
this.el.src = defaultUrl;
}
showError() {
this.el.src = errorUrl;
}
onEnterViewport() {
this.showImage();
}
async fetchImage() {
if (typeof fetch !== "function") {
this.thowError();
throw new Error("浏览器不支持fetch接口");
}
// 如果已经加载过,直接返回
if (!this.blob) {
const target = this;
return fetch(this.img)
.then((res) => {
if (res.status > 199 && res.status < 300) return res.blob();
else return Promise.reject();
})
.then((blob) => {
if (/image/.test(blob.type)) return URL.createObjectURL(blob);
else return Promise.reject();
})
.then((url) => {
target.blob = url;
})
.catch(() => {
target.showError();
throw new Error("URL不正确或MIME类型不正确");
});
}
}
}
const scrollListener = new ScrollListener(),
main = document.getElementsByClassName("main")[0],
imageControllers = images.map((url) => {
const controller = new ImageController(url, main);
scrollListener.addController(controller);
});
</script>
</html>
Author 回答者: LMW-lmw
方案二有那么一点点抖动,这里重新实现了一下
<!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>
<style>
* {
margin: 0px;
padding: 0px;
}
body {
margin: 0px;
padding: 0px;
}
img {
display: block;
}
</style>
</head>
<body>
<div class="demo">
<img
data-src="https://cdn.pixabay.com/photo/2021/08/24/15/38/sand-6570980_960_720.jpg"
alt="1"
/>
<img
data-src="https://cdn.pixabay.com/photo/2013/02/21/19/06/drink-84533_960_720.jpg"
alt="2"
/>
<img
data-src="https://cdn.pixabay.com/photo/2013/07/18/20/26/sea-164989_960_720.jpg"
alt="3"
/>
<img
data-src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg"
alt="4"
/>
<img
data-src="https://cdn.pixabay.com/photo/2017/03/26/21/54/yoga-2176668_960_720.jpg"
alt="5"
/>
</div>
</body>
<script>
const demo = document.querySelectorAll("img");
function lazy() {
for (let elem of demo) {
if (
elem.getBoundingClientRect().top <
document.documentElement.clientHeight
) {
if (elem.dataset.src && elem.src == "") {
elem.src = elem.dataset.src;
}
}
}
}
function throttle(t, fn) {
let time;
return function () {
if (!time) {
time = setTimeout(() => {
time = null;
fn();
}, t);
}
};
}
lazy();
window.addEventListener("scroll", throttle(500, lazy));
</script>
</html>
Author 回答者: gethin036
方法三的简单 demo
<!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>3.IntersectionObserver API + DataSet API</title>
<style>
* {
margin: 0px;
padding: 0px;
}
body {
margin: 0px;
padding: 0px;
}
img {
width: 100%;
height: 600px;
}
</style>
</head>
<body>
<img src="https://cdn.pixabay.com/photo/2021/08/24/15/38/sand-6570980_960_720.jpg" alt="1" />
<img src="https://cdn.pixabay.com/photo/2013/02/21/19/06/drink-84533_960_720.jpg" alt="2" />
<img data-src="https://cdn.pixabay.com/photo/2014/12/15/17/16/boardwalk-569314_960_720.jpg" alt="3" />
<img data-src="https://cdn.pixabay.com/photo/2013/07/18/20/26/sea-164989_960_720.jpg" alt="4" />
<img data-src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg" alt="5" />
<img data-src="https://cdn.pixabay.com/photo/2017/03/26/21/54/yoga-2176668_960_720.jpg" alt="6" />
<img data-src="https://cdn.pixabay.com/photo/2015/03/17/14/05/sparkler-677774_960_720.jpg" alt="7" />
<script>
const images = document.querySelectorAll('img')
// 新的 api IntersectionObserver
const observer = new IntersectionObserver((changes) => {
changes.forEach(change => {
if (change.isIntersecting) {
const img = change.target
// if (img.dataset.src && img.src == "") {
// img.src = img.dataset.src
// }
img.dataset.src && img.src == "" && (img.src = img.dataset.src)
observer.unobserve(img)
}
})
})
images.forEach(img => observer.observe(img))
</script>
</body>
</html>
> Author
回答者: [yanshuaidong](https://github.com/yanshuaidong)
在vue中实现图片懒加载
https://github.com/wangkaiwd/vue-image-lazy
> Author
回答者: [MSpringy](https://github.com/MSpringy)
> **方法三的简单 demo**
>
> ```
> <!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>3.IntersectionObserver API + DataSet API</title>
> <style>
>
> * {
> margin: 0px;
> padding: 0px;
> }
>
> body {
> margin: 0px;
> padding: 0px;
> }
>
> img {
> width: 100%;
> height: 600px;
> }
> </style>
> </head>
> <body>
> <img src="https://cdn.pixabay.com/photo/2021/08/24/15/38/sand-6570980_960_720.jpg" alt="1" />
> <img src="https://cdn.pixabay.com/photo/2013/02/21/19/06/drink-84533_960_720.jpg" alt="2" />
> <img data-src="https://cdn.pixabay.com/photo/2014/12/15/17/16/boardwalk-569314_960_720.jpg" alt="3" />
> <img data-src="https://cdn.pixabay.com/photo/2013/07/18/20/26/sea-164989_960_720.jpg" alt="4" />
> <img data-src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg" alt="5" />
> <img data-src="https://cdn.pixabay.com/photo/2017/03/26/21/54/yoga-2176668_960_720.jpg" alt="6" />
> <img data-src="https://cdn.pixabay.com/photo/2015/03/17/14/05/sparkler-677774_960_720.jpg" alt="7" />
> <script>
> const images = document.querySelectorAll('img')
>
> // 新的 api IntersectionObserver
> const observer = new IntersectionObserver((changes) => {
> changes.forEach(change => {
> if (change.isIntersecting) {
> const img = change.target
> // if (img.dataset.src && img.src == "") {
> // img.src = img.dataset.src
> // }
> img.dataset.src && img.src == "" && (img.src = img.dataset.src)
> observer.unobserve(img)
> }
> })
> })
>
> images.forEach(img => observer.observe(img))
> </script>
> </body>
> </html>
> ```
@gethinzz
intersectionObserver这个方式,我试了,但是第三张出现到视口的时候,下面的图片全部一起加载完毕了。。。 isIntersecting 和 intersectionRatio的值都是一致的,这跟我理解的不一样,是我理解错了吗
> Author
回答者: [croatialu](https://github.com/croatialu)
IntersectionObserver 也可以去做一些广告曝光统计。
我之前做过一个 统计 banner 广告曝光次数的需求,在用户看到这个 banner 的时候,去上报一下
> Author
回答者: [zhengaimin](https://github.com/zhengaimin)
> > **方法三的简单 demo**
> > ```
> > <!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>3.IntersectionObserver API + DataSet API</title>
> > <style>
> >
> > * {
> > margin: 0px;
> > padding: 0px;
> > }
> >
> > body {
> > margin: 0px;
> > padding: 0px;
> > }
> >
> > img {
> > width: 100%;
> > height: 600px;
> > }
> > </style>
> > </head>
> > <body>
> > <img src="https://cdn.pixabay.com/photo/2021/08/24/15/38/sand-6570980_960_720.jpg" alt="1" />
> > <img src="https://cdn.pixabay.com/photo/2013/02/21/19/06/drink-84533_960_720.jpg" alt="2" />
> > <img data-src="https://cdn.pixabay.com/photo/2014/12/15/17/16/boardwalk-569314_960_720.jpg" alt="3" />
> > <img data-src="https://cdn.pixabay.com/photo/2013/07/18/20/26/sea-164989_960_720.jpg" alt="4" />
> > <img data-src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg" alt="5" />
> > <img data-src="https://cdn.pixabay.com/photo/2017/03/26/21/54/yoga-2176668_960_720.jpg" alt="6" />
> > <img data-src="https://cdn.pixabay.com/photo/2015/03/17/14/05/sparkler-677774_960_720.jpg" alt="7" />
> > <script>
> > const images = document.querySelectorAll('img')
> >
> > // 新的 api IntersectionObserver
> > const observer = new IntersectionObserver((changes) => {
> > changes.forEach(change => {
> > if (change.isIntersecting) {
> > const img = change.target
> > // if (img.dataset.src && img.src == "") {
> > // img.src = img.dataset.src
> > // }
> > img.dataset.src && img.src == "" && (img.src = img.dataset.src)
> > observer.unobserve(img)
> > }
> > })
> > })
> >
> > images.forEach(img => observer.observe(img))
> > </script>
> > </body>
> > </html>
> > ```
>
> @gethinzz intersectionObserver这个方式,我试了,但是第三张出现到视口的时候,下面的图片全部一起加载完毕了。。。 isIntersecting 和 intersectionRatio的值都是一致的,这跟我理解的不一样,是我理解错了吗
这里需要将图片给一个默认高度,因为页面滚动的时候,懒加载的图片都没有宽高,所以滚动判断会认为该元素已经在可视区域了
你可以在 img.dataset.src && img.src == "" && (img.src = img.dataset.src) 这一句上面加一个断点,就能看到懒加载图片都是破损状态,但是都在可视区域内了
> Author
回答者: [justorez](https://github.com/justorez)
`getBoundingClientRect` with loading: https://codepen.io/justorez/pen/rNbmZwz
```html
<style>
body {
height: 100vh;
}
img {
display: block;
width: 600px;
margin-bottom: 10px;
}
</style>
<script type="module">
import { throttle } from 'https://esm.sh/lodash-es'
const imgList = [
'https://cdn.pixabay.com/photo/2016/03/23/04/01/woman-1274056_1280.jpg',
'https://cdn.pixabay.com/photo/2018/03/06/22/57/portrait-3204843_960_720.jpg',
'https://cdn.pixabay.com/photo/2017/03/26/21/54/yoga-2176668_960_720.jpg',
'https://cdn.pixabay.com/photo/2017/08/07/16/39/girl-2605526_1280.jpg',
'https://cdn.pixabay.com/photo/2017/03/30/18/17/girl-2189247_960_720.jpg',
'https://cdn.pixabay.com/photo/2016/12/19/21/36/woman-1919143_960_720.jpg',
'https://cdn.pixabay.com/photo/2021/08/24/15/38/sand-6570980_960_720.jpg',
'https://cdn.pixabay.com/photo/2013/02/21/19/06/drink-84533_960_720.jpg',
'https://cdn.pixabay.com/photo/2013/07/18/20/26/sea-164989_960_720.jpg',
'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg'
].map(url => {
const img = document.createElement('img')
img.src = 'https://www.icegif.com/wp-content/uploads/loading-icegif-1.gif'
img.dataset.src = url
document.body.append(img)
return img
})
const lazyLoad = throttle(() => {
for (const img of imgList) {
if (
img.getBoundingClientRect().top <
document.body.clientHeight
) {
if (img.dataset.src) {
img.src = img.dataset.src
}
}
}
}, 200)
setTimeout(lazyLoad, 1000) // 展示 loading 效果
document.addEventListener('scroll', lazyLoad)
</script>
```