大概结构如下
<Container>
<Item>xxxx</Item>
<Item>xxxx</Item>
<Item>xxxx</Item>
<Item>xxxx</Item>
<Item>
<div>
<A />
</div>
</Item>
.....
</Container>
注意: 目标元素的父元素不是滚动容器,中间隔了好几层;
A组件提供了一个onClick方法,参数是e,
需求是,点击A组件的时候,希望将A组件滚动到容器可视区的顶部
思路:
- 获取到A组件点击时,距离容器顶部的距离, 记录为等待滚动的距离top1;
- 获取Container组件已经滚动了的距离,记录为已滚动的记录 top0;
- 那么只要Container.scrollTo(0, top0+top1),就可以完成需求
- top1的获取思路:通过A组件click时的e参数,可以获取到点击时,点击位置距离浏览器页面顶部的y坐标,同方法可以获取到container距离浏览器页面顶部的y坐标,相减就可以得到top1,代码如下
const top1 = e.target.getBoundingClientRect().y -
containerRef.current.getBoundingClientRect().y
containerRef.current是React中容器的dom实例,可以等效于 document.getElementById获取到的dom
- top0的获取思路
const top0=containerRef.current.scrollTop
- 所以A组件的onClick函数为
onClick = e =>{
xxx计算top0和top1的操作
containerRef.current.scrollTo(0, top0+top1);
}
- 还有个问题,这个A组件点开后,可能会有展开dom的操作,可能是个折叠面板,所以需要等到展开后再去计算新的top,所以需要加个timeout来延迟触发(原理可以去看js执行宏任务与微任务的区别)
onClick = e =>{
setTimeout(() => {
xxx计算top0和top1的操作
containerRef.current.scrollTo(0, top0+top1);
})
}
-
后面发现不对,会报错,还突然拿不到实例了
image.png
这意思是说,这onClick是react的合成事件,在调用后,e就直接销毁了,在settimeout里面根本拿不到e,如果不想让他销毁,就调用 e.persisit()
参考文档: https://reactjs.org/docs/legacy-event-pooling.html
所以最终代码如下
onClick = e =>{
e.persisit();
setTimeout(() => {
xxx计算top0和top1的操作
containerRef.current.scrollTo(0, top0+top1);
})
}