React中如何在列表Div渲染至屏幕前将指定子元素滚动到顶部以避免视觉卡顿
React中如何在列表Div渲染至屏幕前将指定子元素滚动到顶部以避免视觉卡顿
嗨,这个问题我太懂了!之前做长列表滚动需求的时候也踩过这个视觉闪烁的坑,刚好有几个靠谱的解决办法,核心就是要在浏览器还没把内容画到屏幕上的时候就完成滚动操作,这样用户完全看不到中间的卡顿。
方案一:函数组件首选——用useLayoutEffect抓准时机
useLayoutEffect和我们常用的useEffect不一样,它是同步执行的:DOM更新完立刻就触发,这时候浏览器还没开始绘制页面,刚好卡在我们需要的时间窗口里。滚动操作完成后,浏览器才会把最终的滚动状态画出来,完全不会有先看到整个列表再跳转的视觉 glitch。
具体代码实现如下:
import { useRef, useLayoutEffect } from 'react'; function TargetScrolledList() { // 给父容器和目标子元素创建ref,用来获取真实DOM节点 const parentContainerRef = useRef(null); const targetChildRef = useRef(null); useLayoutEffect(() => { // 确保两个ref都成功关联到DOM元素 if (parentContainerRef.current && targetChildRef.current) { // 计算目标子元素相对于父容器的顶部偏移量 const targetOffsetTop = targetChildRef.current.offsetTop; // 设置父容器的scrollTop,直接让目标元素顶到容器顶部 parentContainerRef.current.scrollTop = targetOffsetTop; } }, []); // 空依赖数组,只在组件首次挂载时执行一次 return ( <div ref={parentContainerRef} key="parent-div" style={{ overflow: 'auto', height: '300px', border: '1px solid #ccc' }} > <div key="child-div-1" style={{ height: '100px', background: '#f5f5f5', padding: '10px' }}>子元素1</div> <div key="child-div-2" style={{ height: '100px', background: '#eeeeee', padding: '10px' }}>子元素2</div> <div ref={targetChildRef} key="child-div-3" style={{ height: '100px', background: '#e0e0e0', padding: '10px' }} >子元素3</div> <div key="child-div-4" style={{ height: '100px', background: '#eeeeee', padding: '10px' }}>子元素4</div> <div key="child-div-5" style={{ height: '100px', background: '#f5f5f5', padding: '10px' }}>子元素5</div> </div> ); }
方案二:类组件兼容方案
如果你还在维护类组件的项目,把相同的逻辑放到componentDidMount生命周期钩子中即可。这个钩子同样是在DOM挂载完成但浏览器还未绘制页面时执行,能达到同样的无闪烁效果:
import React from 'react'; class TargetScrolledList extends React.Component { constructor(props) { super(props); this.parentContainerRef = React.createRef(); this.targetChildRef = React.createRef(); } componentDidMount() { if (this.parentContainerRef.current && this.targetChildRef.current) { const targetOffsetTop = this.targetChildRef.current.offsetTop; this.parentContainerRef.current.scrollTop = targetOffsetTop; } } render() { return ( <div ref={this.parentContainerRef} key="parent-div" style={{ overflow: 'auto', height: '300px', border: '1px solid #ccc' }} > <div key="child-div-1" style={{ height: '100px', background: '#f5f5f5', padding: '10px' }}>子元素1</div> <div key="child-div-2" style={{ height: '100px', background: '#eeeeee', padding: '10px' }}>子元素2</div> <div ref={this.targetChildRef} key="child-div-3" style={{ height: '100px', background: '#e0e0e0', padding: '10px' }} >子元素3</div> <div key="child-div-4" style={{ height: '100px', background: '#eeeeee', padding: '10px' }}>子元素4</div> <div key="child-div-5" style={{ height: '100px', background: '#f5f5f5', padding: '10px' }}>子元素5</div> </div> ); } }
小提醒
- 一定要给父容器设置固定高度和
overflow: auto(或overflow: scroll),不然父容器不会产生滚动条,滚动操作也就无法生效。 - 依赖数组要设为空,确保滚动逻辑只在组件首次挂载时执行一次,避免后续组件更新时重复触发滚动。
备注:内容来源于stack exchange,提问作者Bear Bile Farming is Torture
相关产品推荐
相关产品推荐

