背景:以前我们在写滚动动画的时候通常判断元素是否显示通常会通过 getBoundingClientRect 获取位置,但是 getBoundingClientRect 将触发重排,利用此技术可能会很快造成性能瓶颈。
关于 IntersectionObserver
IntersectionObserver 是 web 领域众多观察器中的一个,是用来是观察元素和窗体相交的状态,很适合用在滚动交互事件,像是懒加载、埋点等场景。
除了 IntersectionObserver ,我们常用的还有用来观察 DOM 变化的 MutationObserver;用来观察元素的尺寸变化 ResizeObserver。
使用
- 创建一个 intersection observer
1 | const callback = (entries) => { |
callback 回调函数将会在主线程中被执行, 其中 entry 属性:
1 | entry.boundingClientRect 目标元素的区域信息 |
Intersection Observer 可用的方法有 observe(),unobserve() 和 disconnect()。
- observe():用来添加观察者要监视的目标元素,观察者可以具有多个目标元素,但是此方法一次只能接受一个目标。
1 | const element = document.querySelector('.animatedElement'); |
- unobserve():用来从观察的元素列表中移除元素。
1 | observer.unobserve(element); |
- disconnect():用来停止观察其所有目标元素。观察者本身仍处于活动状态,但没有目标。在 disconnect() 之后,目标元素仍然可以通过 observe() 传递给观察者。
1 | observer.disconnect(); |
利用 getBoundingClientRect 实现动画
通常是监听滚动,通过获取元素位置
1 | window.addEventListener("scroll", () => checkForVisibility); |
根据元素的位置触发动画
1 | function checkForVisibility() { |
但是 getBoundingClientRect 将触发重排也就会造成性能问题,那 getBoundingClientRect() 为什么会触发 Reflow 呢?
在 chromium 的源码中搜索 getBoundingClientRect 可以看到代码:
1 | DOMRect* Range::getBoundingClientRect() const { |
BoundingRect() 中调用了 UpdateStyleAndLayout
1 | FloatRect Range::BoundingRect() const { |
其中 UpdateStyleAndLayout 方法调用之后将会触发 LayoutTree 的重新渲染,也就是 Reflow。
利用 IntersectionObserver 实现动画
通过 Intersection Observer API 获取到两个元素重叠部分的准确值,只需几行代码即可设置根据元素可见性触发动画:
1 | const animationObserver = new IntersectionObserver((entries, observer) => { |
IntersectionObserver-polyfill
对于不支持的浏览器可以引入 w3c 官方创建的 polyfill https://github.com/w3c/IntersectionObserver。
参考资料
• https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
• https://www.zhangxinxu.com/wordpress/2020/12/js-intersectionobserver-nav/