【问题标题】:Infinite Scroll from Scratch in Reactjs w/ Intersection Observer使用 Intersection Observer 在 Reactjs 中从头开始无限滚动
【发布时间】:2020-02-13 17:52:55
【问题描述】:

背景:我使用交叉点观察器构建了一个无限滚动器,目的是重用 dom 节点(也称为窗口)。当 div 滚动离开视口的顶部时,它会绝对定位到 div 序列的末尾,反之亦然,对于退出底部的 div。

问题:如果您向上滚动太快,您会完全滚动到 div 之外。同时,如果您向下滚动太快,则不会发生这种情况。很难弄清楚原因。

Codesandbox(原创):https://codesandbox.io/s/cranky-pasteur-wckzm
Codesandbox(更新):https://codesandbox.io/s/infinite-scroll-intersection-observer-p15bm

代码(更新):

/* eslint no-unused-vars: 0 */
/* eslint react-hooks/exhaustive-deps: 0 */
/* eslint no-sequences: 0 */

import React, { useState, useEffect } from "react";
import "./styles.css";

const App = () => {
  useEffect(() => {
    const divs = document.querySelectorAll('.abc')

    const options = {
      root: null,
      threshold: 0,
      rootMargin: '0px'
    }

    let observer
    if (observer) observer.disconnect()
    observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting || window.pageYOffset === 0) return

        entry.target.onclick = function(){console.log(entry)}
        const firstDiv = entry.target.parentNode.firstElementChild
        const lastDiv = entry.target.parentNode.lastElementChild
        const lastDivTopAmount = Number(lastDiv.style.top.split('').slice(0, -2).join(''))
        const firstDivTopAmount = Number(firstDiv.style.top.split('').slice(0, -2).join(''))

        if (entry.boundingClientRect.top < 0) {
          console.log('element intersected top')
          firstDiv.style.top = `${lastDivTopAmount+320}px`
          firstDiv.parentNode.appendChild(firstDiv)
        }

        else {
          console.log('element intersected bottom')
          lastDiv.style.top = `${firstDivTopAmount-320}px`
          lastDiv.parentNode.prepend(lastDiv)
        }
      })
    }, options)

    divs.forEach(div => {
      observer.observe(div)
    })
}, [])

  return (
    <div>
      <div className="visibleRows">
        <div 
          style={{top: '0px', backgroundColor: 'blue', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-1 abc"
        />
        <div
          style={{top: '320px', backgroundColor: 'red', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-2 abc"
        />
        <div
          style={{top: '640px', backgroundColor: 'green', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-3 abc"
        />
        <div
          style={{top: '960px', backgroundColor: 'purple', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-4 abc"
        />
        <div
          style={{top: '1280px', backgroundColor: 'yellow', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-5 abc"
        />
        <div 
          style={{top: '1600px', backgroundColor: 'pink', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-6 abc"
        />
        <div 
          style={{top: '1920px', backgroundColor: 'orange', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-7 abc"
        />
        <div 
          style={{top:'2240px', backgroundColor: 'cyan', height: '320px', width: '580px', outline: '4px solid pink'}}
          className="baby-div-8 abc"
        />
      </div>
  </div>
  );
};

export default App;

样式.css

.abc {
  position: absolute;
  height: 320px;
  width: 580px;
  outline: 4px solid pink;
}

【问题讨论】:

  • 我认为我无法在运行 Chrome 的 Mac 上重现。向下滚动永远,向上滚动在当前 div 的顶部停止(以慢速和快速速度)。我是不是误会了?
  • @sallf 你在codesandbox上全屏打开demo了吗?如果您尝试在默认代码框窗口中演示代码,则会发生您描述的问题。
  • 是的,我在代码框“浏览器”窗口和全屏窗口 (wckzm.csb.app) 中进行了尝试。在这两种情况下,结果都与我上面描述的相同。
  • @salif 我忘了保存css文件。现在就试试。工作吗?
  • 是的,我现在可以重现该错误。它是向上滚动到无穷大的期望结果,还是应该在到达页面顶部时停止?

标签: javascript reactjs performance scroll infinite-scroll


【解决方案1】:

IntersectionObserver 回调仅在观察到的条目穿过 threshold 时触发。由于您在选项中定义的threshold0(并且您的根是null),这意味着只有当您的一个框移动穿过threshold(也就是当一个框进入或退出视口)。

令人困惑的部分是entries.forEach(...) 不会遍历所有观察到的目标,它只会获取超过阈值的实体。

因此,您的第一个条件 if (entry.boundingClientRect.top &lt; 0 &amp;&amp; window.pageYOffset &gt; 320) { ... } 有效,因为当您的框从视口顶部滚动时可以实现这些条件。但是,您的第二个条件 if (entry.boundingClientRect.bottom &gt; 1250 &amp;&amp; window.pageYOffset &gt; 320) { ... } 中的条件可能永远不会满足,因为您的视口需要为 931bottom &gt; 1250 (931 + 320 &gt; 1250)。

我不太确定如何让您的方法与 IntersectionObserver 一起使用,因为您无法保证在需要时会超过阈值。如果顶框以负数 top 开头,并且您只是跟踪“视口顶部”阈值,这会有点工作......但它可能会因快速滚动而出现问题。

这对于scroll event listener 来说可能是一个更好的工作...或者使用交叉点观察器在到达页面底部后复制所有 div,而不是实际移动它们。

更新

尝试进一步澄清:第二个条件将“可能永远不会满足”,因为在任何短于931px 的屏幕上,当它“离开视口”时不会有boundingClientRect.bottom &gt; 1250。在下图中,即使底部的灰色框满足该条件,当蓝色框超过0 阈值时,它也不会作为entity 传递。

现在,即使您确实有足够高的视口(或调整了数字以匹配 window.innerHeight),它仍然不起作用,因为如果您向上滚动刚好足以将蓝色框移到底部 - 当向下滚动时,不会有任何框越过您的阈值,因此不会触发回调,也不会出现蓝色的回调。

【讨论】:

  • 我不明白为什么第二个条件“可能永远不会被满足”。每当 div 相交或离开视口时,我们都会获得一个条目。当 div 滚动到页面底部时,将向 if 语句提供一个条目。在它滚出页面时,.bottom 将大于视口,因为它超过了视口的整个高度。第二个 if 语句在慢速滚动上得到满足这一事实难道不是你的陈述,即第二个 if 条件可能永远不会得到满足吗?
  • 顺便说一句,如果您检查更新的代码,则第二个条件仅被替换为 else 语句。其他不需要评估的案例现在立即返回。效果好一点,但如果你向上滚动的速度足够快,仍然会出错。
  • @RobertC 我有点不确定你的困惑在哪里,所以我添加了两个澄清点。更新后第一个似乎没有太大帮助,但看看第二个是否有帮助。无论如何,现实世界的应用程序是什么?可能值得注意的是,以这种方式与 DOM 交互并不是 React 中的最佳实践。
  • 认为您误解了我的旧沙箱中的代码,但也许我误解了您的第二点。并非所有条目都相交。我专门提供已经超出视口的条目,这就是为什么两个条件被包装在一个检查相交错误的条件中的原因。该条目是发送到第二个条件的内容。然后它检查该 div 是否已经在底部左侧的视口之外。因此,在您中间的 img 中,蓝色框触发了第二个 if,并且附加到顶部的不是该条目,而是 visiblerows 的底部 div。
  • 这就是为什么我能够消除第二个 if 条件并使其成为 else,以及为什么我现在立即返回相交的 div。我们不需要检查条目是否从顶部到一定长度,只要不小于0,并且在视口之外(意味着它必须从底部离开) .这就是为什么它现在是另一个。如果您执行一些控制台日志,您会发现 if 条件仅适用于设计上已设置为相交 false 的 div。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-01
  • 1970-01-01
  • 2021-02-01
相关资源
最近更新 更多