React如何监听滚动获取视口内#parent的可见子div列表
实现方案
优先用浏览器原生IntersectionObserver实现,比手动监听scroll事件计算位置性能好很多,不会频繁触发重排阻塞主线程。
首先先修正你代码里的笔误:id为parent的容器闭合标签写错了,基础结构参考:
<div id="parent"> <!-- 遍历渲染的列表项 --> <div id={item.id}>{item.title}</div> </div>
方案1:IntersectionObserver 实现(推荐)
这个API会自动监听元素和视口/指定滚动容器的交叉状态,不需要绑定高频触发的滚动事件。
// 存当前可见的元素,用Set方便增删去重 const visibleItems = new Set() const parentEl = document.getElementById('parent') // 初始化观察器 const itemObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // 元素进入可见范围,加入集合 visibleItems.add(entry.target) } else { // 元素离开可见范围,移出集合 visibleItems.delete(entry.target) } }) // 这里随时能拿到当前所有可见的子div列表 console.log('可见元素列表:', Array.from(visibleItems)) }, { // 根节点默认是浏览器视口,如果是检测parent容器内部滚动的可见性,改成 root: parentEl root: null, // 可见阈值:0代表元素刚露1px就算可见,1代表完全露出才算可见,支持传数组配置多触发点 threshold: 0 }) // 把parent下所有子div注册到观察器 // 如果列表是动态渲染的,每次新增列表项后要给新元素单独调用observe方法 Array.from(parentEl.querySelectorAll('div')).forEach(itemEl => { itemObserver.observe(itemEl) }) // 不需要监听时(比如页面/组件卸载)调用销毁方法 // itemObserver.disconnect()
方案2:scroll事件监听实现(兼容老旧浏览器)
如果要兼容不支持IntersectionObserver的老浏览器,可以用这个方案,一定要加防抖,否则滚动时计算太频繁会卡顿。
// 防抖工具函数 function debounce(fn, delay = 100) { let timer = null return function(...args) { clearTimeout(timer) timer = setTimeout(() => fn.apply(this, args), delay) } } const parentEl = document.getElementById('parent') // 计算可见元素的逻辑加防抖 const calcVisibleItems = debounce(() => { const allChildDivs = Array.from(parentEl.querySelectorAll('div')) const viewportHeight = window.innerHeight || document.documentElement.clientHeight const viewportWidth = window.innerWidth || document.documentElement.clientWidth const visibleList = allChildDivs.filter(el => { const rect = el.getBoundingClientRect() // 判断元素是否和视口有交叉 return ( rect.top < viewportHeight && rect.bottom > 0 && rect.left < viewportWidth && rect.right > 0 ) }) console.log('可见元素列表:', visibleList) return visibleList }) // 绑定滚动、窗口大小变化事件 window.addEventListener('scroll', calcVisibleItems) window.addEventListener('resize', calcVisibleItems) // 页面初始化时先执行一次拿初始可见元素 calcVisibleItems() // 不需要监听时记得移除事件 // window.removeEventListener('scroll', calcVisibleItems) // window.removeEventListener('resize', calcVisibleItems)
注意事项
- 如果列表是动态增删的,两个方案都要做对应处理:IntersectionObserver方案要给新增元素注册监听、给移除元素取消监听;scroll方案不需要额外处理,每次计算都会拿全量子元素判断。
- 可见判断规则可以按业务调整:比如要求元素露出一半才算可见,就修改判断条件,或者把IntersectionObserver的threshold改成0.5。
- 如果是在局部滚动容器(不是整页滚动)里检测,IntersectionObserver要把root设为对应的滚动容器,scroll方案要把视口宽高的计算换成滚动容器的宽高和滚动位置。
内容的提问来源于stack exchange,提问作者Chandler Bing
相关产品推荐
相关产品推荐

