大佬网址:
https://blog.csdn.net/weixin_47136265/article/details/132303570
<template>
<view class="main">
<scroll-view scroll-y="true" class="left-content">
<view class="left-item" :class="{ 'activeItem': tabIndex == index }" v-for="(item,index) in leftData"
:key="item.id" @click="clickLeftItem(index)">{{item.title}}</view>
</scroll-view>
<scroll-view scroll-y="true" class="right-content" :scroll-into-view="scrollId" scroll-with-animation
@scroll="scrollEvt">
<view class="right-item" v-for="(item,index) in rightData" :key="item.id" :id="'item'+index">
<view class="title">
{{item.title}}
</view>
<view class="content">
<view class="content-item" v-for="itm in item.children" :key="itm.id">
{{itm.text}}
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
tabIndex: 0,
scrollId: '',
distanceList: [],
timer: null,
isLeftClick: false,
leftData: [{
title: '第一个',
id: 10001
},
{
title: '第二个',
id: 10002
},
{
title: '第三个',
id: 10003
},
{
title: '第四个',
id: 10004
},
{
title: '第五个',
id: 10005
},
{
title: '第六个',
id: 10006
}
],
rightData: [{
id: '20001',
title: '第一部分标题',
children: [{
id: '30001',
text: '第一个子项'
},
{
id: '30002',
text: '第二个子项'
},
{
id: '30003',
text: '第三个子项'
},
{
id: '30004',
text: '第四个子项'
}
]
},
{
id: '20002',
title: '第二部分标题',
children: [{
id: '30005',
text: '第一个子项'
},
{
id: '30006',
text: '第二个子项'
},
{
id: '30007',
text: '第三个子项'
}
]
},
{
id: '20003',
title: '第三部分标题',
children: [{
id: '30008',
text: '第一个子项'
},
{
id: '30009',
text: '第二个子项'
},
{
id: '30010',
text: '第三个子项'
},
{
id: '30011',
text: '第四个子项'
}
]
},
{
id: '20004',
title: '第四部分标题',
children: [{
id: '30012',
text: '第一个子项'
},
{
id: '30013',
text: '第二个子项'
}
]
},
{
id: '20005',
title: '第五部分标题',
children: [{
id: '300014',
text: '第一个子项'
},
{
id: '300015',
text: '第二个子项'
}
]
},
{
id: '20006',
title: '第六部分标题',
children: [{
id: '300016',
text: '第一个子项'
},
{
id: '300017',
text: '第二个子项'
},
{
id: '300018',
text: '第三个子项'
},
{
id: '300019',
text: '第四个子项'
}
]
}
]
}
},
mounted() {
setTimeout(() => {
this.getDistanceToTop();
}, 500)
},
methods: {
clickLeftItem(index) {
this.isLeftClick = true
this.tabIndex = index
this.scrollId = 'item' + index
},
getDistanceToTop() { //获取右侧各部分距离顶部的距离
let that = this
let selectorQuery = uni.createSelectorQuery().in(this);
selectorQuery.selectAll('.right-item').boundingClientRect(function(rects) {
rects.forEach(function(rect) {
that.distanceList.push(rect.top)
})
console.log('that.distanceList', that.distanceList);
}).exec()
},
// 元素滚动到顶部时,对应的左侧导航栏变为选中状态
scrollEvt(e) {
// 点击左侧导航栏引起的滚动不做判断
if (this.isLeftClick) {
this.isLeftClick = false
return
}
// 防抖
if (this.timer) {
clearTimeout(this.timer)
}
this.timer = setTimeout(() => {
let scrollTop = e.detail.scrollTop //滚动的高度
// 找到位于顶部元素的索引,距离大于滚动高度的第一个元素的上一个元素就是此时位于顶部的元素
let index = this.distanceList.findIndex(it => {
// 滚动条的位置大于元素距离顶部位置的距离时,说明元素已经滑过了顶部
return (it > scrollTop)
}) - 1
if (index == this.tabIndex) return
this.tabIndex = index
}, 200)
}
}
}
</script>
<style lang="less" scoped>
.main {
display: flex;
justify-content: space-between;
width: 100vw;
height: calc(100vh - 44px);
}
.left-content {
width: 250rpx;
height: 100%;
background-color: #E7E7E7;
}
.left-item {
width: 100%;
height: 100rpx;
text-align: center;
line-height: 100rpx;
}
.activeItem {
background-color: #fff;
color: skyblue;
}
.right-content {
flex: 1;
height: 100%;
background-color: #f4f4f4;
}
.content-item {
width: 100%;
height: 200rpx;
background-color: aqua;
margin-top: 20rpx;
}
.title {
padding: 15px 0 0 10px;
}
</style>
大佬
<template>
<view class="bodys">
<view class="scroll_box" id="scroll_box">
<scroll-view :style="{ height: scrollHeight + 'px' }" scroll-y='true' class="left_box"
:scroll-into-view="leftIntoView">
<view class="left_item" v-for="(item,i) in leftArray" :key='i' @click="onLeft" :data-index="i"
:id="'left-'+i" :class="{select:i == leftIndex}">
{{item}}
</view>
</scroll-view>
<scroll-view :style="{ height: scrollHeight + 'px' }" @scroll="mainScroll" :scroll-into-view="scrollInto"
scroll-y='true' class="right_box" scroll-with-animation="true">
<slot></slot>
<view class="right_item" v-for="(item,i) in rightArray" :key='i' :id="'item-'+i">
<view class="rigth_title">
{{item.title}}
</view>
<view class="lis" v-for="(items,j) in item.list" :key='j'>
{{items}}
</view>
</view>
<view class="fill-last" :style="{ height: fillHeight + 'px' }"></view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
name: "side-navigation",
data() {
return {
leftArray: [],
rightArray: [],
scrollHeight: 400,
scrollInto: "",
leftIndex: 0,
topArr: [],
scrollTopSize: 0,
fillHeight: 0, // 填充高度,用于最后一项低于滚动区域时使用
}
},
computed: {
/* 计算左侧滚动位置定位 */
leftIntoView() {
return `left-${this.leftIndex > 3 ? this.leftIndex - 3 : 0}`;
}
},
mounted() {
/* 等待DOM挂载完成 */
this.$nextTick(() => {
/* 在非H5平台,nextTick回调后有概率获取到错误的元素高度,则添加200ms的延迟来减少BUG的产生 */
setTimeout(() => {
/* 等待滚动区域初始化完成 */
this.initScrollView().then(() => {
/* 获取列表数据,你的代码从此处开始 */
this.getListData();
});
}, 200);
});
},
methods: {
/* 初始化滚动区域 */
initScrollView() {
return new Promise((resolve, reject) => {
let view = uni.createSelectorQuery().select('#scroll_box');
view.boundingClientRect(res => {
console.log(res);
this.scrollTopSize = res.top;
this.scrollHeight = res.height;
this.$nextTick(() => {
resolve();
});
}).exec();
});
},
// 获取数据
getListData() {
new Promise((resolve, reject) => {
uni.showLoading();
setTimeout(() => {
let [left, main] = [
[],
[]
];
for (let i = 0; i < 25; i++) {
left.push(`${i + 1}类商品`);
let list = [];
let r = Math.floor(Math.random() * 10);
r = r < 1 ? 3 : r;
for (let j = 0; j < r; j++) {
list.push(j);
}
main.push({
title: `第${i + 1}类商品标题`,
list
});
}
// 将请求接口返回的数据传递给 Promise 对象的 then 函数。
resolve({
left,
main
});
}, 1000);
}).then(res => {
uni.hideLoading();
this.leftArray = res.left;
this.rightArray = res.main;
// DOM 挂载后 再调用 getElementTop 获取高度的方法。
this.$nextTick(() => {
this.getElementTop();
});
});
},
// 获取元素顶部信息
getElementTop() {
new Promise((resolve, reject) => {
let view = uni.createSelectorQuery().selectAll('.right_item');
view.boundingClientRect(data => {
resolve(data);
}).exec();
}).then(res => {
console.log(res);
let topArr = res.map(item => {
return item.top - this.scrollTopSize; /* 减去滚动容器距离顶部的距离 */
});
this.topArr = topArr;
/* 获取最后一项的高度,设置填充高度。判断和填充时做了 +-20 的操作,是为了滚动时更好的定位 */
let last = res[res.length - 1].height;
if (last - 20 < this.scrollHeight) {
this.fillHeight = this.scrollHeight - last + 20;
}
});
},
// 点击左侧导航
onLeft(e) {
const index = e.currentTarget.dataset.index;
// this.leftIndex = index
this.scrollInto = `item-${index}`
},
// 右侧滑动
mainScroll(e) {
let top = e.detail.scrollTop;
let index = 0;
/* 查找当前滚动距离 */
for (let i = this.topArr.length - 1; i >= 0; i--) {
/* 在部分安卓设备上,因手机逻辑分辨率与rpx单位计算不是整数,滚动距离与有误差,增加2px来完善该问题 */
if (top + 2 >= this.topArr[i]) {
index = i;
break;
}
}
this.leftIndex = index < 0 ? 0 : index;
},
},
}
</script>
<style>
page,.bodys {
height: 100%;
}
.scroll_box {
display: flex;
height: 100%;
}
.left_box {
width: 30%;
}
.left_item {
height: 80rpx;
}
.lis {
height: 200rpx;
border-radius: 10rpx;
background: #808080;
color: #FFFFFF;
text-align: center;
line-height: 200rpx;
margin-bottom: 10rpx;
}
.select {
background-color: #4CD964;
}
</style>
网址1
网址2