实现原理

data-src 属性为真实图片路径

1
<img src="default.jpg" data-src="www.example.com/1.jpg">
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
const lazy = (el) => {
let scrTop = getTop();
let windowHeight = document.documentElement.clientHeight;
function getTop(){
return document.documentElement.scrollTop || document.body.scrollTop;
}
function getOffset(node){
return node.getBoundingClientRect().top + scrTop;
}
function inView(node){
// 设立阈值
const threshold = 0;
const viewTop = scrTop;
const viewBot = viewTop + windowHeight;

const nodeTop = getOffset(node);
const nodeBot = nodeTop + node.offsetHeight;

const offset = (threshold / 100) * windowHeight;
console.log((nodeBot >= viewTop - offset), (nodeTop <= viewBot + offset))
return (nodeBot >= viewTop - offset) && (nodeTop <= viewBot + offset)
}
function check(node){
let el = document.querySelector(node);
let images = [...el.querySelectorAll('img')];
images.forEach(img => {
if(inView(img)){
img.src = img.dataset.src;
}
})
}
check(el);
}

window.onscroll = function(){
lazy('.foo');
}

IntersectionObserver

可以异步监听目标元素与其祖先或视窗(viewport)处于交叉状态的方式。祖先元素与视窗(viewport)被称为根(root)

  • root: 这是用于观察的根元素。他定义了可观察元素的基本捕获框架,默认情况下,root指向的是浏览器的视口,但实际上可以是任意的DOM元素,要注意的是:root在这种情况下,要观察元素的必选要在root代表的Dom元素内部
  • rootMargin: 计算交叉时添加到根(root)边界盒bounding box的矩形偏移量, 可以有效的缩小或扩大根的判定范围从而满足计算需要。值得选项与marginCSS类似,比如rootMargin: ‘50px 20px 10px 40px’(top, right, bottom, left)
  • threshold: 一个包含阈值的list, 升序排列, list中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会生成一个通知(Notification)。如果构造器未传入值, 则默认值为0.

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// config对象和回调函数一起传递到Observer构造函数
const config = {
root: null,
rootMargin: '0px',
threshold: 0.5
}
let observer = new IntersectionObserver(fucntion(entries, self){
// 回调函数处理逻辑
}, config)

// 给 IntersectionObserver 实际观察的元素
const images = document.querySelecttorAll('img');
images.forEach(image => {
observer.observe(image)
})

注意事项

观察的元素必须位于 root 代表的DOM元素中
IntersectionObserver 一次只能接受一个观察元素,不支持批量观察,如果观察多个元素,需要遍历分别观察

entries

回调函数作为Array是特殊类型:IntersectionObserverEntry

  • rootBounds: ‘捕捉框架(root + rootMargin)’的矩形
  • boundClientRect: 观察元素本身的矩形
  • intersectionRect: 捕捉框架和观察元素相交的矩形

isIntersecting: 返回观察元素是否与捕获框架相交
intersctionRatio:利于计算的遍历属性,返回 intersectionRect 与 boundingClientRect 的比例值.

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const imgs = document.querySelectorAll('[data-src]')
const config = {
rootMargin: '0px',
threshold: 0
}
let observer = new IntersectionObserver((entries, self) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 加载图像
preloadImage(entry.target)
self.unobserve(entry.target)
}
})
}, config)

imgs.forEach(img => {
observer.observe(img)
})

preloadImage = (img) => {
const src = img.dataset.src
if (!src) { return; }
img.src = src
}

延迟加载的优点

在优化内容加载和简化最终用户体验之间达成平衡
用户可以更快加载到内容,局部加载
网站看到更多的用户保留,不断向用户提供内容,减少用户离开网站的可能
网站看到较低的资源成本,按需加载内容

兼容性

IE和safari暂时还不支持,chrome,firefox, edge支持性良好

polyfill