如何判断图片是否在可视区域
# 前言
在性能优化方面,实现图片懒加载的功能是一项必备的技能。在实现过程时,我们需要判断图片是否在可视区域。由于页面绘制的复杂性性,需要 Check if an element is visible in a scrollable container
,而网上搜出的99.99%
的答案都是 Check if an element is visible within window's viewport
,我也去搜索了stackoverflow
里头的答案,高赞的解决方案很多,但是把这些函数拷贝到自己的 Demo
中却是始终不生效,于是就有了这篇文章:
首先约定本文涉及到的变量含义:
- 可滚动的容器:
container
- 元素:
element
# 方案一:[github]HTML DOM: offset+scrollTop
在搜索的过程中发现一个宝藏网站:https://htmldom.dev/ (opens new window)
这个网站里头罗列了几十种常见的DOM操作,没事可以多翻一翻学习,有机会一定要翻译成中文,放在github
的网站里头。
滚动的三种状态:
The following functions return true
if the ele
element is visible in its scrollable container:
const isVisible = function (ele, container) {
const eleTop = ele.offsetTop;
const eleBottom = eleTop + ele.clientHeight;
const containerTop = container.scrollTop;
const containerBottom = containerTop + container.clientHeight;
return (
// element 完全在container
(eleTop >= containerTop && eleBottom <= containerBottom) ||
// element 刚进入container
(eleTop < containerTop && containerTop < eleBottom) ||
// element 即将离开container
(eleTop < containerBottom && containerBottom < eleBottom)
);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用方法:
var container = document.querySelector('#container');
var ele = document.querySelector('#ele');
if (isVisible(ele,container)) {
// Do something...
}
2
3
4
5
# 方案二:[github]HTML DOM: ele.getBoundingClientRect()
还有一个函数推荐的比较多element.getBoundingClientRect()
,与上面的offset
定位的方式不同的是,使用时不需要对postion
的定位方式做要求,根据实验可得:
element.getBoundingClientRect()
:元素相对于windows
的Viewport
的距离。client.getBoundingClientRect()
:包在元素外层的container
到Viewport
的距离。
因此,一般来说,
client
要小于element
的距离。
注解:虽然网上推荐此函数比较多,但是大部分的场景还是在判断
element
是否在哎window
的viewport
可视区域,而如果要用于识别是否在container
可视区域,就需要将两者相减,即将两个scrollTop
相减后与element
本身的height
相比。如果需要画图说明的话,还是使用
relative
比较清楚。
但是这里需要注意一下,bottom
和right
的的参考和传统的定位有所区别,始终以上边框和左边框作为基准线:
const { bottom, height, top1 } = ele.getBoundingClientRect();
const { top2, bottom2} = container.getBoundingClientRect();
// 这里用一个三元表达式,将上面的滚动的三种方式已经囊括在里头了。
return top1 <= top2 ? top2 - top1 <= height : bottom1 - bottom2 <= height;
2
3
4
# 方案三:IntersectionObserver API
上面两种方案都使用到了**window.scroll
监听 Element.getBoundingClientRect()
并使用 _.throttle
节流**,浏览器其实已经内置了一个监听器完成这些操作了,可以通过读取changes[item]
的intersetionRatio
和isIntersecting
属性判断是否元素重叠。
this.observer = new IntersectionObserver((changes) => {
// changes: 目标元素集合(需要取一下才可以)
console.log('intersectionRatio', changes[0].intersectionRatio);
console.log('isIntersecting', changes[0].isIntersecting);
});
2
3
4
5
# 🚨 注意:position的设置
在调试以上代码的时候,调试至少花费了有4个小时,总是被几个非常基础的问题给卡住,主要开在的是css
属性的设置上,尤其是offset
的取值上,由于没有考虑到position
的设置,offset
总是计算element
到 windows
视图窗的高度。
将position
设置为relative
的前提条件,也是在stackoverflow (opens new window)另一个问题的解答中提到的,如果它不提估计我又要卡好久,感谢感谢,stackoverflow
真yyds
!
# 兼容性说明:
不同浏览器在获取window
的可视高度方法是不同的,考虑到兼容性,常可以看到以下写法:
let vieportWidth = (window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
let vieportHeight = (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight)
2
还有一个例子,就是当需要获取页面的滚动高度scrollTop
时,经常写成下面这段代码:
var scrollTop = window.pageYOffset // 兼容 FireFox
|| document.documentElement.scrollTop
|| document.body.scrollTop
|| 0;
2
3
4
在 IE 和 Firefox 中:
- 当页面指定
DOCTYPE(DTD)
时,使用document.documentElement
。- 当页面没有指定
DOCTYPE(DTD)
时,使用document.body
。
# 总结
scrollHeight
:只有container
才有scroll
相关属性,选中element
无效果。scrollTop
:一般情况下为0
,只有滚动起来才可会有值,【滚动时:不变】
offsetTop
:其值取决于container
上设置的position
值。如果
position:static
,返回的是element
到windows
的距离如果
position:relative
,返回的是element
到container
的距离。
# 使用 lodash 进行优化
import _ from "lodash"
class Example extends Component {
constructor(props) {
super(props);
// 加入 防抖 操作
this.isVisible3 = _.throttle(this.isVisible2,1000)
}
isVisible = (container, ele) => {
const eleTop = ele.offsetTop;
const eleBottom = eleTop + ele.clientHeight;
const containerTop = container.scrollTop;
const containerBottom = containerTop + container.clientHeight;
return (
(eleTop >= containerTop && eleBottom <= containerBottom) ||
(eleTop < containerTop && containerTop < eleBottom) ||
(eleTop < containerBottom && containerBottom < eleBottom)
);
};
isVisible2 = (container, ele) => {
const { bottom, height, top } = ele.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
return top <= containerRect.top ? containerRect.top - top <= height : bottom - containerRect.bottom <= height;
};
componentDidMount() {
const container = document.querySelector(".container")
const ele = document.getElementById("ele")
oContainer.addEventListener('scroll', () => {
console.log(this.isVisible3(oContainer, ele))
})
}
}
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