You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中向div顶部追加元素时保持滚动状态的实现方案

解决React横向滚动容器头部添加元素时的滚动位置偏移问题

问题场景

实现横向滚动的日期数据容器,初始展示当天及后续日期,支持左右无限滚动加载。向容器尾部追加元素时表现正常,但向头部添加过往日期元素时,滚动位置会自动偏移,导致用户当前视图被打乱,且无法提前预知新增元素的宽度。

解决方案

核心思路是在添加左侧元素前记录当前滚动位置,DOM更新后计算新增元素的实际占用宽度,调整滚动位置抵消偏移,确保用户视图保持不变。具体实现步骤如下:

1. 容器引用与状态跟踪

  • 使用useRef获取滚动容器的DOM引用,用于读取和修改滚动位置。
  • 用另一个ref记录添加左侧元素前的滚动位置,避免状态更新时丢失数据。

2. 修改添加左侧元素的逻辑

添加元素前先保存当前滚动位置,再更新数据数组。

3. DOM更新后调整滚动位置

使用useLayoutEffect在DOM更新后同步执行滚动位置调整,避免视觉闪烁。计算新增元素的实际宽度(包含margin),将滚动位置加上该宽度,抵消新增元素带来的左移。

完整代码实现

function App(props) {
  const [array, setArray] = React.useState(['100', '321', '32', '412', '12', '33', '54']);
  const containerRef = React.useRef(null);
  const prevScrollLeftRef = React.useRef(0);

  const addLeft = () => {
    // 记录添加前的滚动位置
    if (containerRef.current) {
      prevScrollLeftRef.current = containerRef.current.scrollLeft;
    }
    const newItem = Math.floor(Math.random() * 300).toString();
    setArray([newItem, ...array]);
  };

  const addRight = () => {
    setArray([...array, Math.floor(Math.random() * 300).toString()]);
  };

  React.useLayoutEffect(() => {
    const container = containerRef.current;
    if (!container || prevScrollLeftRef.current === 0) return;

    // 获取新增的第一个子元素
    const firstChild = container.firstElementChild;
    if (!firstChild) return;

    // 计算元素实际占用宽度(含margin)
    const styles = window.getComputedStyle(firstChild);
    const marginLeft = parseInt(styles.marginLeft, 10);
    const marginRight = parseInt(styles.marginRight, 10);
    const totalItemWidth = firstChild.offsetWidth + marginLeft + marginRight;

    // 调整滚动位置,保持视图不变
    container.scrollLeft = prevScrollLeftRef.current + totalItemWidth;
    // 重置滚动位置记录
    prevScrollLeftRef.current = 0;
  }, [array]);

  return (
    <div className='App'>
      <button onClick={addLeft}>add left</button>
      <button onClick={addRight}>add right</button>
      <div className="container" ref={containerRef}>
        {array.map((i, index) => (
          <div key={index} className="element" style={{ minWidth: `${i}px` }}>
            {i}
          </div>
        ))}
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector("#app"))
.container {
  background-color: red;
  width: 550px;
  height: 120px;
  margin: auto;
  display: flex;
  overflow: auto;
}

.element {
  height: 100px;
  background-color: yellow;
  margin: 10px;
  font-size: 30px;
  color: black;
  display: flex;
  align-items: center;
  justify-content: center;
}

关键说明

  • useLayoutEffect:在DOM更新完成后立即执行,确保能获取到最新的子元素尺寸,避免滚动调整时出现视觉闪烁。
  • 实际宽度计算:通过getComputedStyle获取元素的margin值,结合offsetWidth得到元素在容器中占用的完整宽度,保证滚动位置调整的准确性。
  • 唯一key:给子元素添加key属性(示例中用index,实际场景建议用唯一标识),确保React正确识别DOM元素的变化。

内容的提问来源于stack exchange,提问作者LLL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 17:03:17