JS 判断是否进入可视区域
什么是 IntersectionObserver
IntersectionObserver 用于监听 DOM 元素是否进入可视区域。相较于 scroll 事件结合 getBoundingClientRect() 实现监听可视区域的方式更为高效灵活。
一、基本用法
IntersectionObserver 是个构造函数。需要使用 new 关键字调用,通过 observe 方法指定监听的元素。
const intersectionObserver = new IntersectionObserver(entries => {
console.log(entries)
});
// 监听单个 DOM 元素是否进入或离开可视区域
intersectionObserver.observe(document.querySelector('#item'))
当该 DOM 元素进入可视区域时输出:
当该 DOM 元素离开可视区域时输出:
其中常用的属性如下:
- boundingClientRect:被观察者的位置宽高;
- intersectionRatio:监听元素可见部分占自身的比例(按矩形算)
- intersectionRect:监听元素可见部分的位置宽高;
- isIntersecting:监听元素是否进入可见区域;
- rootBounds:根元素的位置宽高;
- time:触发的时间;
- target:监听的元素。
二、取消监听
与 removeEventListener
同理,需要取消监听。
1、取消单个监听
可通过 unobserve 取消指定节点的 intersectionObserver 监听。
const intersectionObserver = new IntersectionObserver(entries => {
console.log(entries)
});
intersectionObserver.observe(document.querySelector('#item'))
// 2s 后取消指定节点的监听
setTimeout(() => {
intersectionObserver.unobserve(document.querySelector('#item'))
}, 2000)
2、取消所有监听
可通过 disconnect 取消所有节点的 intersectionObserver 监听。
const intersectionObserver = new IntersectionObserver(entries => {
console.log(entries)
});
intersectionObserver.observe(document.querySelector('#item1'))
intersectionObserver.observe(document.querySelector('#item2'))
// 2s 后取消所有节点的监听
setTimeout(() => {
intersectionObserver.disconnect()
}, 2000)
在 React 中使用时,一般会在 useEffect 的返回值中对 IntersectionObserver 统一销毁:
useEffect(() => {
const intersectionObserver = new IntersectionObserver(entries => {
console.log(entries)
});
intersectionObserver.observe((triggerNode.current as any))
return () => intersectionObserver.disconnect()
}, [])
三、应用场景
- 图片的懒加载
- 列表的无限滚动
- 计算广告元素的曝光情况
- 可点击链接的预加载
- ...
图片懒加载示例:
import React, { useEffect, useRef } from 'react'
import { imgList } from './constant'
import './ImageLazy.less'
const listData = Array.from({ length: 50 }, (_, i) => i + 1)
function ObserverTestPage() {
const observeRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
const callback: IntersectionObserverCallback = (enters) => {
for (const item of enters) {
const { isIntersecting, target }: IntersectionObserverEntry & { target: any } = item
if (isIntersecting) {
if (target.nodeName === 'IMG' && !target.src) {
target.src = target.dataset.src
observe.unobserve(target)
}
}
}
}
const observe = new IntersectionObserver(callback)
const imgTag = document.querySelectorAll('img[data-src]')
imgTag.forEach(v => observe.observe(v))
// 组件销毁的时候,停止所有监听
return () => observe.disconnect()
}, [])
return (
<div className="ImageLazy">
<div className="plac">
{listData.map(v => <div key={v}>{v}</div>)}
</div>
<div ref={observeRef} className="observe">
this is dom
</div>
<div className="image-content">
{imgList.map((v, i) => (
<div key={i} className="image-wrap">
<img data-src={v} alt="" />
</div>
))}
</div>
</div>
)
}
export default ObserverTestPage