页面

锚点滚动 和 滚动自动激活菜单视频案例
javascript
<template>
<view class="container">
<!-- 锚点导航 -->
<view class="anchor-tabs">
<view
v-for="tab in subjectTabs"
:key="tab.id"
class="tab"
:class="[currentSubject === tab.id ? 'activeTab': '']"
@click="switchSubjectTab(tab.id)"
>{{ tab.subjectName }}</view>
</view>
<!-- 滚动内容区域 -->
<scroll-view
class="scroll-view"
:scroll-into-view="currentSubject"
scroll-y
:scroll-with-animation="true"
@scroll="onScroll"
>
<view
v-for="section in subjectRightSections"
:id="section.id"
:key="section.id"
class="content-section"
></view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
currentSubject: 'sec_0', // 当前滚动到的学科元素id
distances: null, // scrollVie里面所有区块距离信息对象
subjectTabs: [], // 学科tabs(根据状态和 学段 查出 所有学科数据,并对所有学科进行集合)
}
},
computed: {
// 查询出的所有学科数据id集合
contentSectionsIds() {
const subjectRightSectionsIds = this.subjectRightSections.map(element => element.id);
return subjectRightSectionsIds
},
},
onShow() {
this.getRightsCardList()
},
methods: {
// 获取所有学科的权益数据
async getRightsCardList() {
// 获取正确的购买信息(学段,年级)
const { data } = await rightsQA.getRightsCardList({
status: this.currentStatus,
studyStageType: this.currentStudyStage + 1
})
const { rightsInfoList, rightsStatusTotalList, studyStageType, subjectList } = data
// 3: 处理状态 + 学段 下的学科list
this.subjectTabs = subjectList.filter(item => {
// 给学科数据添加id
item['id'] = `sec_${item.subjectType}`
return item
})
// 4: 权益数据list
this.subjectTabs.forEach(item => {
this.subjectRightSections.push({
id: item.id,
subjectType: item.subjectType, // 学科枚举,多学科有唯一id
subject: item.subject, // 多学科使用逗号分隔,学科枚举集合
subjectName: item.subjectName,
subjectRightList: this.rightsInfoListFun(item.subjectType,rightsInfoList)
})
})
// 5: 处理默认激活学科
this.currentSubject = this.subjectTabs[0]?.id
// 6: 获取滚动区域中所有内容区块的距离信息
if (rightsInfoList && rightsInfoList.length > 0) {
this.getAllSectionsDistance()
} else {
this.noRightShow = true // 么有数据展示无数据背景图
}
},
// 整理各个学科权益list数据
rightsInfoListFun(subjectType, rightsInfoList) {
// 筛选某个学科的所有权益list
let subjectRightList = rightsInfoList.filter(rightsItem => rightsItem.subjectType === subjectType)
/*
1: rightType: 权益类型(付费权益卡:【0.付费】; 图书权益卡:【1.体验 2.正式 3.预售】)
2: 付费购买的答疑卡放在图书免费配赠权益的上面【升序排序】
3: 付费购买的答疑卡按照购买答疑卡的时间倒序排列
4: 图书免费配赠的答疑权益按照图书激活的时间倒序排列。
*/
const subjectPayRightList = subjectRightList.filter(rightsItem => rightsItem.rightType === 0).sort((a, b) => {
return new Date(b.rightsPayTime) - new Date(a.rightsPayTime);
});
const subjectFreeRightList = subjectRightList.filter(rightsItem => rightsItem.rightType !== 0).sort((a, b) => {
return new Date(b.bookUserCreateTime) - new Date(a.bookUserCreateTime);
});
return [...subjectPayRightList, ...subjectFreeRightList]
},
// 学科tab切换,自动滚动到对应学科部分
switchSubjectTab(sectionId) {
this.currentSubject = sectionId;
},
// 步骤1: 获取滚动区域中所有内容区块的距离信息
async getAllSectionsDistance() {
this.$nextTick(async() => {
try {
// 使用 Promise 和 await 让异步代码更清晰
const [scrollViewRect, ...targetRects] = await Promise.all([
this.getBoundingClientRect('.scroll-view'),
...this.contentSectionsIds.map(id =>
this.getBoundingClientRect(`#${id}`)
)
]);
if (scrollViewRect) {
// 计算每个元素距离 scroll-view 顶部的距离
const distances = this.contentSectionsIds.map((id, index) => {
const targetRect = targetRects[index];
let distanceObj = {
id: id,
distance: null,
error: '元素未找到'
}
if (targetRect) {
distanceObj = {
id: id,
distance: targetRect.top - scrollViewRect.top,
elementTop: targetRect.top, // 当前元素节点的上边界坐标
scrollViewTop: scrollViewRect.top // scrollView元素节点的上边界坐标
};
}
return distanceObj;
});
console.log('所有区块距离信息:', distances);
this.distances = distances
}
// if (targetRect && scrollViewRect) {
// // 计算目标元素顶部相对于 scroll-view 顶部的距离
// // 因为 boundingClientRect 返回的是相对于视口顶部的距离,所以做减法
// this.distance = targetRect.top - scrollViewRect.top;
// console.log('距离:', this.distance);
// }
} catch (error) {
console.error('获取节点信息失败:', error);
}
});
},
// 封装获取节点边界信息的方法
getBoundingClientRect(selector) {
return new Promise((resolve) => {
// 创建查询请求
uni.createSelectorQuery()
.in(this) // 限定在当前组件实例中查找
.select(selector)
.boundingClientRect((rect) => {
// 如果节点存在,rect 是一个对象,包含位置和尺寸信息;否则为 null
if (rect) {
resolve(rect);
} else {
resolve(null);
}
})
.exec(); // 执行查询
});
},
// 步骤2: scroll滚动事件回调,自动激活对应区域学科
onScroll(e) {
const { scrollTop, scrollHeight } = e.detail;
// 常用的属性包括:
// scrollTop: 竖向滚动条位置
// scrollLeft: 横向滚动条位置
// scrollHeight: 滚动内容的高度
// scrollWidth: 滚动内容的宽度
// deltaX, deltaY: 前一次滚动事件的偏移量
// 例如:计算已滚动比例
// const scrollRatio = scrollTop / scrollHeight;
// if (!this.distances) {
// this.getAllSectionsDistance()
// return
// }
console.log('竖向滚动距离顶部距离:', scrollTop);
const currentSection = this.findInterval(scrollTop, this.distances)
if (currentSection) {
console.log(`当前在区块 ${JSON.stringify(currentSection)}`);
console.log(`当前在区块序号 ${currentSection.index + 1}`);
this.currentSubject = currentSection.id;
}
},
// 步骤3: 计算滚动距离 和 每个科学id元素的坐标 直接的差值,并确定当前滚动在那个科学id区域内
findInterval(number1, intervals) {
let number = number1 + 5 // 给一个误差范围5
// let number = number1
// 确保区间数组是升序排列的
const sortedIntervals = [...intervals].sort((a, b) => a.distance - b.distance);
for (let i = 0; i < sortedIntervals.length - 1; i++) {
if (number >= sortedIntervals[i].distance && number < sortedIntervals[i + 1].distance) {
return {
interval: [sortedIntervals[i].distance, sortedIntervals[i + 1].distance],
index: i,
id: sortedIntervals[i].id,
description: `在区间 [${sortedIntervals[i].distance}, ${sortedIntervals[i + 1].distance}) 内`
};
}
}
// 如果数字小于第一个区间点
if (number < sortedIntervals[0].distance) {
return {
interval: [-Infinity, sortedIntervals[0].distance],
index: -1,
id: sortedIntervals[0].id,
description: `小于 ${sortedIntervals[0].distance}`
};
}
// 如果数字大于最后一个区间点
if (number >= sortedIntervals[sortedIntervals.length - 1].distance) {
return {
interval: [sortedIntervals[sortedIntervals.length - 1].distance, Infinity],
index: sortedIntervals.length - 1,
id: sortedIntervals[sortedIntervals.length - 1].id,
description: `大于等于 ${sortedIntervals[sortedIntervals.length - 1].distance}`
};
}
return null;
},
}
}
</script>
<style lang="scss" scoped>
.scroll-view {
flex: 1; /* 撑满剩余空间 */
// height: 100%; /* 重要:设置高度 */
// height: calc(100vh - 400rpx);
height: calc(100vh - 470rpx);
}
.content-section:last-child {
height: 100% !important;
}
</style>