Fork me on GitHub

JS-图片懒加载

图片懒加载

图片懒加载

为什么需要图片懒加载

默认情况下,访问多图片的页面时,即使图片不在用户当前视窗内,也会会占据用户的带宽,影响其他资源加载,影响用户的体验。

图片懒加载的原理

1. 当页面加载时加载一个尺寸很小的占位图片(1kb 以下),然后再通过 js 选择性的去加载真正的图片。

实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//html
<img data-src="./img/06bccedb4361daafc7575fdaaccfb617.jpg" src="./img/121212.jpg" />

//js
(function lazyLoad() {
const imageToLazy = document.querySelectorAll('img[data-src]');
const loadImage = function (image) {
image.setAttribute('src', image.getAttribute('data-src'));
image.addEventListener('load', function () {

image.removeAttribute("data-src");
})
}

imageToLazy.forEach(function (image) {
loadImage(image);
})
})()

测试发现,必须使用 script 的 src 引入 js 文件,否则图片加载的优先级不变。
1

2. 滚动到视窗内再加载图片
运用Intersection Observer 我们可以做到当图片滚动到视窗后再加载该图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//js
(function lazyLoad() {
const imageToLazy = document.querySelectorAll('img[data-src]');
const loadImage = function (image) {
image.setAttribute('src', image.getAttribute('data-src'));
image.addEventListener('load', function () {
console.log(image)
image.removeAttribute("data-src");
console.log(1, image)
})
}
const myObserver = new IntersectionObserver(function (items, observer) {
items.forEach(item => {
if (item.isIntersecting) {
loadImage(item.target);
observer.unobserve(item.target)
}
})
})
imageToLazy.forEach(function (image) {
myObserver.observe(image)
})
})()

3. 原生 JS 实现懒加载
API:

  • document.documentElement.clientHeight/window.innerHTML,获取屏幕的可视区域(根元素 HTML)的高度
  • document.documentElement.scrollTop,获取页面根元素(HTML)顶部与浏览器窗口顶部之间的距离,也就是滚动条的滚动距离
  • element.offsetTop,获取当前元素相对于文档顶部的高度
    2
    元素是否在可视区域判断公司:offsetTop-scrollTop<clientHeight
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  (function lazyLoad() {
let images = document.querySelectorAll('img[data-src]');
//offsetTop是元素与offsetParent的距离,循环获取直到页面顶部
function getTop(e) {
let T = e.offsetTop;
while (e = e.offsetParent) {
T += e.offsetTop;
}
return T
}

const loadImage = function (image) {
let src = image.getAttribute("data-src")
image.setAttribute("src", src);
};
const lazy = function () {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
let clientHeight = document.documentElement.clientHeight
for (let i = 0; i < images.length; i++) {
let image = images[i];
let offsetTop = getTop(image);
if (scrollTop + clientHeight >= offsetTop) {
loadImage(image)
}
}
}

function debounce(fun, wait, immediate) {
let timeout
return () => {
clearTimeout(timeout)

if (immediate) {
let is = timeout;
setTimeout(() => {
timeout = null
}, wait);
if (!is) {
fun();
}
} else {
setTimeout(() => {
fun();
timeout = null
}, wait)
}
}
}
//页面加载完先执行一次
lazy()
document.addEventListener('scroll', debounce(lazy, 1000, true))
})()

React 实现图片懒加载

实现节流 hook

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useState, useEffect } from "react";
const useThrottle = (fn, wait, deps = []) => {
const [time, setTime] = useState(0);
useEffect(() => {
let curr = Date.now();
if (curr - time > wait) {
fn();
setTime(curr);
}
}, deps);
};

export default useThrottle;

图片加载 hook

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import React, { useState, useEffect, useRef } from "react";
import useThrottle from "./useThrottle";

const useImageLazy = (domList) => {
const [scrollCount, setScrollCount] = useState(0);

const getTop = (dom) => {
let { top } = dom.getBoundingClientRect();

return top;
};
const loadImg = (image) => {
image.setAttribute("src", image.getAttribute("data-src"));
image.onload = () => {
image.removeAttribute("data-src");
};
};
const onload = () => {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
let clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
let len = domList.length;
for (let i = 0; i < len; i++) {
let image = domList[i];
let top = getTop(image);
if (top < scrollTop + clientHeight) {
if (image.getAttribute("data-src")) {
loadImg(image);
}
}
}
};

const scroll = () => {
let count = scrollCount + 1;
setScrollCount(count);
};
useEffect(() => {
document.addEventListener("scroll", scroll);

return () => {
document.removeEventListener("scroll", scroll);
};
});

useThrottle(
() => {
onload();
},
200,
[scrollCount]
);
};
export default useImageLazy;

使用

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
import React, { useRef } from "react";
import useImageLazy from "./useImageLazy";

const imgList = Array.from({ length: 8 }, (item, index) => (item = `https://www.maojiemao.com/public/svg/gen${index + 5}.png`));

const style = {
display: "block",
width: "300px",
height: "300px",
marginTop: "50px"
};

const Test = () => {
const domRef = useRef([]);
useImageLazy(domRef.current);

return (
<>
{imgList.map((item, index) => (
<img ref={(el) => (domRef.current[index] = el)} key={`lazy-${index}`} data-src={item} style={style} />
))}
</>
);
};

export default Test;
-------------本文结束感谢阅读-------------