Skip to main content

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