一、需求描述
将导航区域与内容区域实现联动,即点击导航区域,内容区滚动到对应位置,内容区滚动过程中根据内容定位到相对应的导航栏。
效果如下:
侧边导航与内容联动效果
二、功能实现思路分析汇总:
三、具体代码
1、功能一
右侧内容区scroll-view的scroll-into-view属性绑定id,当触发左侧导航2的点击事件时设置subjectId的值,从而实现滚动到对应区域。
<!-- 右侧内容区 -->
<scroll-view
:scroll-y="true"
scroll-with-animation
:scroll-into-view="subjectId"
class="deep-right"
@scroll="scrolling"
>
<view
v-for="(subject, index) in subjectList"
:id="'subject' + index"
:key="index"
class="subject-con"
>
<view
v-for="(son, i) in subject.sonList"
:key="i"
class="subject-item"
@click="goToDetail(son)"
>
<view>{{ son.code }}</view>
<view>{{ son.name }}</view>
</view>
</view>
</scroll-view>
const subjectId = ref(''); //scroll-into-view 绑定值
const navChange = (index: any) => { // 点击左侧导航栏触发
navAvtive.value = index;
subjectId.value = 'subject' + index;
};
2、功能二
2.1、方案一
滚动方法中,获取各个块级元素距离视口顶部的距离Top1,获取导航区域1距离视口顶部的距离Top2,将Top1与Top2比较,Top1<Top2即代表块级元素滚动到导航1的下方了。
const scrolling = (e: any) => {
//subjectList 是右侧内容区的数据
subjectList.value.forEach((item, index) => {
//动态循环判断 Top1和Top2 的大小,将比较过程封装成方法调用
if (isInViewport(document.getElementById(`subject${index}`))) {
if (slideNavList.value) {
// 因为我这里左侧导航是封装的子组件,因此调用左侧导航组件的setTabActive方法,修改导航激活tab
slideNavList.value.setTabActive(index);
}
}
});
};
动态比较过程的方法封装
const isInViewport = (element: any) => {
//getBoundingClientRect() JS的方法,用于获取元素位置和尺寸
// careerMap-nav 是顶部横向导航1块元素的Id
const rect = element.getBoundingClientRect();
return (
// 起初这里用的是Math.abs(rect.top),发现上滑时左侧导航定位不准,打印发现 上滑过程中rect.top会起初是赋值然后逐渐增加
rect.top <=
document.getElementById('careerMap-nav')?.getBoundingClientRect()?.bottom
//careerMap-nav 元素下边界相对于视口顶部的距离
);
};
Js中的getBoundingClientRect · 前端押题宝典 · 看云getBoundingClientRect()方法相关指路:Js中的getBoundingClientRect · 前端押题宝典 · 看云
2.2方案二:
// 获取一级学科距离父元素的top值
const subjectPosition = ref([])
const getPositionTop = async () => {
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query
.selectAll(".subject-con")
.boundingClientRect((data) => {
console.log("得到布局位置信息" + JSON.stringify(data));
let height = 0
data.forEach((item: any) => {
// console.log("节点顶部离页面顶部的距离为" + item.top);
// item.height累加
height = height+item.height
subjectPosition.value.push(height); //和父元素的距离
});
console.log("subjectPosition.value",subjectPosition.value);
})
.exec();
}, 100)
}
// 找到scrollTop介于数组中哪两个元素之间
const getBounds =(number:any, sortedArray:any) =>{
let lowerBound = -1;
let upperBound = sortedArray.length;
for (let i = 0; i < sortedArray.length; i++) {
if (sortedArray[i] === number) {
// 如果数组中存在给定的数字,则直接返回其索引
return { lowerBound: i, upperBound: i };
} else if (sortedArray[i] > number) {
// 如果当前元素大于给定的数字,则找到了上界
upperBound = i;
break; // 因为数组是升序的,所以不需要再继续查找
}
// 否则,当前元素小于给定的数字,继续查找
lowerBound = i;
}
// 如果循环结束仍未找到上界,upperBound保持数组长度
return { lowerBound, upperBound: upperBound < sortedArray.length ? upperBound : sortedArray.length - 1 };
}
const slideNavList = ref();
const scrolling = (e: any) => {
console.log("scrollTop", e.detail.scrollTop);
const { lowerBound, upperBound }=getBounds( e.detail.scrollTop,subjectPosition.value)
console.log(lowerBound, upperBound);
if (slideNavList.value) {
slideNavList.value.setTabActive(upperBound);
}
}