当我快速滚动时,Intersection Observer 有时会失败

Zar*_*ech 15 javascript intersection-observer

我对 Intersection Observer API 有一个小问题,它对我很有用,但是。

当我在网页上快速(非常快)滚动时,Intersection Observer API 有时无法检测到视口中元素的伪装。

当我缓慢/正常滚动时,它适用于所有元素

观察者选项:

RootMargin: 0px 0px -40px 0px,
threshold: 0.7,
root: null
Run Code Online (Sandbox Code Playgroud)

元素高度:介于100px和之间200px

有谁知道为什么?

83C*_*C10 8

Intersection Observer 在循环中运行一个异步函数来检查观察到的 DOM 元素的位置。它与浏览器的渲染周期相结合,虽然它发生得非常快(大多数设备为 60fps,或每 16.66 毫秒一次),但如果您移动滚动条的速度比这些检查发生的速度快,IO API 可能无法检测到某些可见性变化。事实上,未被检测到的元素甚至还没有被渲染。

这是有道理的,因为 IO 的主要目标是检查元素是否对人眼可见。根据Intersection Observer 规范,其目的是提供一种简单且最佳的解决方案来延迟或预加载图像和列表、检测电子商务广告的可见性等。

话虽如此,Intersection Observer 是一个期待已久的功能,它解决了很多问题,但并不适合所有用例。如果你能接受这样一个事实,即在高速滚动的情况下它不会捕捉到一些交叉点——太好了。如果没有,那么还有其他选择。

解决方案 1:推断相交元素

处理此问题的一种方法是尝试间接推断哪些元素穿过视口但未被 Intersection Observer 捕获。要实现它,您需要按升序为所有列表元素提供唯一的数字属性。然后,在每个 Intersection Observer 的回调函数中,保存相交元素的最小和最大 ID。在回调结束时,调用setTimeout(applyChanges, 150)通过与之间ID的所有元件以调度功能,其将循环minmax没有被IO删去。此外,放在clearTimeout()回调的开头,以确保此函数等待 IO 处于非活动状态一段时间。

let minId = null;
let maxId = null;
let debounceTimeout = null;

function applyChanges() {
  console.log(minId, maxId);
  const items = document.querySelectorAll('.item');
  // perform action on elements with Id between min and max
  minId = null;
  maxId = null;
}

function reportIntersection(entries) {
  clearTimeout(debounceTimeout);
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const entryId = parseInt(entry.target.id);
      if (minId === null || maxId === null) {
        minId = entryId;
        maxId = entryId;
      } else {
        minId = Math.min(minId, entryId);
        maxId = Math.max(maxId, entryId);
      }
    }
  });
  debounceTimeout = setTimeout(applyChanges, 500);
}

const container = document.querySelector('#container');
const items = document.querySelectorAll('.item');
const io = new IntersectionObserver(reportIntersection, container);
let idCounter = 0;
items.forEach(item => {
  item.setAttribute('id', idCounter++);
  io.observe(item)
});
Run Code Online (Sandbox Code Playgroud)

解决方案 2:限制滚动事件

不可能影响默认滚动条行为,但您可以使用自定义滚动条覆盖它。这个想法是限制滚动事件,这样容器在每个渲染周期不能滚动超过它自己的高度。这样,IntersectionObserver将有时间捕获所有交叉点。虽然我建议使用现有的库来完成这项任务,但下面是它应该如何工作的粗略逻辑。

首先,定义容器在单个渲染周期内可以滚动的最大距离:let scrollPool = 500。在每个滚动事件上,安排scrollPool在下次渲染requestAnimationFrame()功能期间将重置为原始值。接下来,检查 是否scrollPool小于0,如果是,return不滚动。否则,减去scrollDistancescrollPool并添加scrollDistance到容器的scrollTop属性。

同样,由于用户可以通过多种方式滚动页面,因此该解决方案实施起来可能有点矫枉过正。但为了描述这个想法,这里有一个wheel活动的草稿:

#container { overflow: hidden; }
Run Code Online (Sandbox Code Playgroud)
let scrollPool = 500;

function resetScrollPool() {
  scrollPool = 500;
}

function scrollThrottle(event) {
  window.requestAnimationFrame(resetScrollPool);
  if (scrollPool < 0) {
    return false;
  }
  const scrollDistance = event.deltaY * 10;
  scrollPool = scrollPool - Math.abs(scrollDistance);
  document.querySelector('#container').scrollTop += scrollDistance;
}

window.addEventListener('wheel', scrollThrottle);
Run Code Online (Sandbox Code Playgroud)

解决方案 3:实现自定义交叉点检测

这意味着IntersectionObserver完全放弃并使用旧方法。这在性能方面肯定会更加昂贵,但您一定会捕获穿过视口的所有元素。这个想法是添加一个滚动处理程序函数来循环遍历容器的所有子项,以查看它们是否越过可见区域。为了提高性能,您可能希望将此函数去抖动一秒左右。可以在以下位置找到一个很好的实现:如何判断 DOM 元素在当前视口中是否可见?

  • 好的!选项 1 听起来是最好的一个。 (2认同)