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
相关产品推荐
相关产品推荐

