背景:渲染几十个表格,页面很长。表格的个数和内容是后端返回的,不固定。
前端要增加一个导航栏,使页面快速滑动到目标表格。
首先根据表格的名字渲染出导航栏中的每个菜单。
点击菜单定位表格的思路:给循环的每个表格加ref,点击菜单,匹配到对应的表格的dom,
使页面滑动指定的距离— dom.offsetTop, 即表格距离上部的距离
页面滑动切换选中色的思路:监听页面滑动的事件onscroll,获取页面滑动的距离,判断页面滑动的距离和表格距离视窗口上部的距离 之间的关系,设置导航的选中色
过程中解决的问题:
1,react获取循环组件的ref
2,锚点定位
3,随着页面的滑动,切换导航栏的选中色
4,吸顶效果
先来看第一个问题,获取循环组件的ref:
useRef可以传数组,不过ref要直接加在dom元素上,在子组件中则无效,所以我在组件外面包裹了一层div
这样在页面渲染时就把每个组件的dom放到useRef中了。因为多次执行,surveyTableRefList 里面的数据有重复的,后面用时去重一下。
const surveyTableRefList = useRef([]);
function getSurveyTableRef(dom, key) {
if (dom) {
surveyTableRefList.current.push({
dom: dom,
key: key
});
}
}
surveyTableRefList.current = removeRepeatObj(surveyTableRefList.current, 'key');
{Object.keys(tableList).map((item) => (
<div ref={(dom) => getSurveyTableRef(dom, item)} key={item}>
<SurveyTableAbleEdit
loading={tableLoading}
key={item}
title={item}
equipType={equipType}
data={tableList[item]}
equipId={equipId}
/>
</div>
第二个问题锚点定位:在点击菜单时根据菜单名字匹配到对应的表格dom,然后让外面有滚动条的页面滚动表格dom的offsetTop距离。这里注意是让最外面有滚动条的dom滚动,而不是让表格滚动。
只有本身有滚动条的元素设置scrollTop才会起作用。
// 点击部件名字时 锚点定位到选择的表格
const clickSearchItem = (partName) => {
// 因为surveyTableRefList.current这个数组里有两遍dom元素,取第二遍的是正确的顺序
const correctDomArr = surveyTableRefList.current.slice(
surveyTableRefList.current.length / 2,
surveyTableRefList.current.length
);
const anchorElement = correctDomArr.find((item) => item.key == partName).dom;
// anchorElement.scrollIntoView({ block: 'start', behavior: 'instant' }); // 不用这个方法了,有bug
// 只有本身有滚动条的元素设置scrollTop才会起作用,所以改变整个页面的scrollTop
equipInfoWrapRef.current.scrollTop = anchorElement.offsetTop - 164;
};
第三个问题页面滑动,切换导航栏的选中色,思路是在页面滚动到上一个表格的中间和这一个表格的中间时,显示这一个表格.。这里在页面滚动时循环表格,判断滚动的距离在哪一个表格上,就设置导航的颜色在哪一个面。
// 页面滚动时切换导航的选中状态; offsetTop 元素距离上部的距离 offsetHeight本身的高度
useEffect(() => {
equipInfoWrapRef.current.onscroll = (e) => {
const correctDomArr = surveyTableRefList.current.slice(
surveyTableRefList.current.length / 2,
surveyTableRefList.current.length
);
//根据页面滚动的距离切换导航的选中状态
for (let i = 0; i < correctDomArr.length; i++) {
if (i > 0) {
if (
correctDomArr[i - 1].dom.offsetTop - 164 + correctDomArr[i - 1].dom.offsetHeight / 2 < e.target.scrollTop &&
e.target.scrollTop < correctDomArr[i].dom.offsetTop - 164 + correctDomArr[i].dom.offsetHeight / 2
) {
mutate('activePart', i);
}
} else {
if (
correctDomArr[i].dom.offsetTop - 200 < e.target.scrollTop &&
e.target.scrollTop < correctDomArr[i].dom.offsetTop - 164 + correctDomArr[i].dom.offsetHeight
) {
mutate('activePart', i);
}
}
}
};
}, []);
第四个问题,吸顶效果,设置组件的样式为position: sticky;粘性定位,设置top:0,距离上部的距离,z-index:2,加大层级
.quickScreenWrap {
padding-bottom: 10px;
position: -webkit-sticky;
position: sticky;
top: 44px; // 因为上面有Tas组件
z-index: 2;
background: #fff;
}