将元素添加到顶部时保持滚动位置

Ily*_*rov 7 javascript scroll reactjs

我正在尝试创建一个可以向左和向右无限滚动的日历。当用户向前(简单)或向后滚动(问题出在这里)时,它必须动态加载新内容。

当我将内容添加到页面末尾时,它工作正常 - 滚动条调整到新的container.scrollWidth. 但是当我必须在开头添加内容时,整个日历会以 400px 的巨大跳跃向右移动,因为container.scrollLeft属性没有改变,现在开头有一个新元素。

我试图通过container.scrollLeft将新创建的元素的宽度增加400px来缓解这种情况。此方法有效,但仅适用于使用鼠标滚轮(shift+鼠标滚轮向侧面滚动)或移动触摸屏滚动时。

如果我使用鼠标拖动滚动条,它会出现故障 - 我猜它一直试图滚动到旧scrollLeft位置而无视我增加了它。

您能否建议一种方法来实现所有滚动方式的这种行为?

如果您能将我指向一个使用这种技术的网站,这样我就可以进行自我调查,那也太好了。

这是我的半工作示例:

function Container() {
  const rectangleWidth = 400;

  const container = React.useRef(null);

  const [leftRectangles, setLeftRectangles] = React.useState([0]);
  const [rightRectangles, setRightRectangles] = React.useState([1, 2, 3, 4]);


    // When we just rendered a new rectangle in the left of our container,
  // move the scroll position back
  React.useEffect(() => {
    container.current.scrollLeft += rectangleWidth;
  }, [leftRectangles]);

  const loadLeftRectangle = () => {
    const newAddress = leftRectangles[0] - 1;
    setLeftRectangles([newAddress, ...leftRectangles]);
  };

  const loadRightRectangle = () => {
    const newAddress = rightRectangles[rightRectangles.length - 1] + 1;
    setRightRectangles([...rightRectangles, newAddress]);
  };

  const handleScroll = (e) => {
    // When there is only 100px of content left, load new content
    const loadingOffset = 100;
    
    if (e.target.scrollLeft < loadingOffset) {
      loadLeftRectangle(e.target);
    } else if (e.target.scrollLeft > e.target.scrollWidth - e.target.clientWidth - loadingOffset) {
      loadRightRectangle(e.target);
    }
  };

  return (
    <div
      className="container"
      onScroll={handleScroll}
      ref={container}
    >
      {leftRectangles.map((address) => (
        <div className="rectangle" key={address}>
          {address}
        </div>
      ))}
      {rightRectangles.map((address) => (
        <div className="rectangle" key={address}>
          {address}
        </div>
      ))}
    </div>
  );
}

ReactDOM.render(<Container />, document.querySelector("#app"))
Run Code Online (Sandbox Code Playgroud)
.container {
  display: flex;
  overflow-x: scroll;
}

.rectangle {
  border: 1px solid #000;
  box-sizing: border-box;
  flex-shrink: 0;
  height: 165px;
  width: 400px;
  font-size: 50px;
  line-height: 165px;
  text-align: center;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Run Code Online (Sandbox Code Playgroud)

jos*_*ike 5

我认为这是一种不应使用本机浏览器滚动区域的情况。如果你想一想,如果滚动条在两个方向上继续无限变长,那么滚动条就没有任何意义。或者它们的唯一含义是“已查看的区域”。滚动条并不是无限区域的好比喻,因此滚动框是一种很糟糕的方式来完成您想做的事情。

如果您以另一种方式思考,您只会显示一组适合已知屏幕宽度的月份。我解决这个问题的方法是将每个日历绝对定位在容器内,并根据用户在虚拟视图中穿梭的位置计算它们在渲染循环中的位置。这还允许您在日历离屏幕太远时将其删除,并在用户滚动到其位置之前在屏幕外创建/缓冲它们以进行显示。它会阻止您拥有任意宽的对象,这最终会减慢渲染速度。

一种方法是简单地对自 1970 年以来的每个月进行编号,并将您的观点视为沿着viewX这条线的分数位置。月份 的x位置应为viewX - x。当用户拖动时,您会viewX反向移动并重新定位缓冲的元素。当用户停止拖动时,您将采用最后一个速度并将其减慢,直到viewX - x为整数。

这将避免大多数跨浏览器问题和偏移问题。它只需要在显示器运动时进行渲染循环。


Ron*_*ton 3

在动态添加内容之前使用read-write scrollLeft来获取滚动位置,然后使用该.scrollLeft方法将滚动位置重新定位回您想要的位置。您可能需要启动一个对话框,显示不确定的进度指示器(或仅显示文本“工作...”),该指示器在过程中显示以防止卡顿

跨浏览器功能的技巧在于,dialog众所周知,该元素在跨设备类型的一致性方面具有挑战性,因此我建议为您的对话框使用 UI 库或构建自己的库,但保持超级简单。该进度指示器将修复屏幕卡顿。

关于卡顿要考虑的另一个功能是CSS 过渡,其中添加的内容(例如块元素)将逐渐淡出/动画到视口中。