思路:使用两个scroll-view,tabbar分类导航使用scrollleft移动,内容联动使用页面滚动onPageScroll监听滚动高度
效果图
<template>
<view class="content" >
<view :class="[isSticky ? 'tab-sticky': '']">
<view class="base-combox" >
<uni-icons type="location-filled" color="#85D8CE" size="25" />
<text class="tag-text">{{base[0].name}}</text>
</view>
<view class="u-tab" :class="[!isSticky ? 'tab-sticky': '']">
<scroll-view scroll-x="true" scroll-with-animation="true" class="u-tab-view menu-scroll-view" :scroll-left="scrollLeft">
<view v-for="(item,index) in tabbar" :key="index" class="u-tab-item" :class="[isActive == index ? 'u-tab-item-active' : '']"
@tap.stop="swichMenu(index)">
<view class="u-line-1">{{item.name}}</view>
</view>
</scroll-view>
</view>
</view>
<scroll-view :scroll-top="scrollTop" scroll-y scroll-with-animation class="right-box" @scroll="upScroll">
<view class="page-view">
<view class="class-item" :id="'item' + index" v-for="(item , index) in tabbar" :key="index">
<view class="item-title">
<text>{{item.name}}</text>
</view>
<view class="item-container">
<view class="thumb-box" v-for="(item1, index1) in item.children" :key="index1" @tap="goList(item1)">
<image v-if="item1.icon != ''" class="item-menu-image" :src="item1.icon" mode=""></image>
<view v-else class="item-menu-image row-c" style="background-color: #F4F6F8;"><text style="font-size: 20rpx;color: #d0d0d0;">加载失败</text></view>
<view class="item-menu-name">{{item1.name}}</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
components: {
},
data() {
return {
moduleData: [],
base:[{name:"请选择地点",id:'0'}],
showType:'horizontally', //vertically,horizontally
menuIcon:'../../static/list_h.png',//../../static/list_v.png
tabbar: [],
isActive : 0,
scrollLeft: 0, // 横向滚动条位置
scrollTop: 0,
baseComboxH: 0, // 地址区的高度
tabbarScrollW: 0, // 导航区宽度
tabbarScrollH: 0, // 导航区高度
isSticky: true, // 是否吸顶
timer: null // 定时器
}
},
onLoad(option) {
this.tabbar = [{id:1,name:"菜单一",children:[{id:11,name:"子菜单一"},{id:12,name:"子菜单二"}]},
{id:2,name:"菜单二",children:[{id:11,name:"子菜单一子菜单一子菜单一子菜单一",icon:"/static/bq2.png"},{id:12,name:"子菜单二"},{id:13,name:"子菜单三"},{id:14,name:"子菜单四"},{id:15,name:"子菜单五"},{id:16,name:"子菜单六"}]},
{id:3,name:"菜单三",children:[{id:21,name:"子菜单一"},{id:22,name:"子菜单二"}]},
{id:3,name:"菜单四",children:[{id:31,name:"子菜单一"},{id:32,name:"子菜单二"}]},
{id:5,name:"菜单五",children:[{id:41,name:"子菜单一"},{id:42,name:"子菜单二"}]},
{id:6,name:"菜单六",children:[{id:51,name:"子菜单一"},{id:52,name:"子菜单二"}]}]
},
onReady() {
},
mounted(){
this.getScrollW();
},
onPageScroll(e){
//console.log(e,e.scrollTop)
if(this.timer){
clearTimeout(this.timer)
}
this.timer = setTimeout(() => { // 节流
this.timer = null;
// scrollHeight为右边菜单垂直中点位置
// let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
// scrollHeight为右边菜单头部位置
let scrollHeight = e.scrollTop + this.tabbarScrollH + this.baseComboxH + 26;
let len = this.tabbar.length
for (let i = 0; i < len-1; i++) {
let height1 = this.tabbar[i].top;
let height2 = this.tabbar[i + 1].top;
// console.log(height2,scrollHeight,height1)
if (scrollHeight >= height1 && scrollHeight < height2) {
this.tabbarStatus(i);
return ;
}
}
if(scrollHeight >= this.tabbar[len-1].top){
this.tabbarStatus(len-1);
return ;
}
}, 10)
},
methods: {
goList(value) {
var item = {title:value.title,labelName:value.labelName}
uni.navigateTo({
url:value.url+'?item='+encodeURIComponent(JSON.stringify(item))
})
},
/**
* 点击上方的tab切换
* @index 传入的 ID
*/
async swichMenu(index) {
if (index == this.isActive ) return;
//this.scrollLeft = 0;
this.$nextTick(function(){
//for (let i = 0; i < index - 1; i++) {
//this.scrollLeft += this.tabbar[i].width
//};
// this.isActive = index;
// // 效果三(当前点击子元素居中展示) 不受子元素宽度影响
// this.scrollLeft = this.tabbar[index].left - this.tabbarScrollW / 2 + this.tabbar[index].width / 2;
this.tabbarStatus(index);
//this.scrollTop = this.tabbar[index].top
uni.pageScrollTo({
duration:200, // 毫秒
scrollTop: (this.tabbar[index].top - this.tabbarScrollH - this.baseComboxH - 18) // 位置
});
// console.log(this.scrollLeft,this.scrollTop)
})
},
// 获取标题区域宽度,和每个子元素节点的宽度以及元素距离左边栏的距离
getScrollW() {
const query = uni.createSelectorQuery().in(this);
query.select('.u-tab-view').boundingClientRect(data => {
// 拿到 scroll-view 组件宽度高度
this.tabbarScrollW = data.width
this.tabbarScrollH = data.height
}).exec();
query.select('.base-combox').boundingClientRect(data => {
// 拿到 base-combox 高度
this.baseComboxH = data.height
}).exec();
query.selectAll('.u-tab-item').boundingClientRect(data => {
let dataLen = data.length;
for (let i = 0; i < dataLen; i++) {
// scroll-view 子元素组件距离左边栏的距离
this.tabbar[i].left = data[i].left;
// scroll-view 子元素组件宽度
this.tabbar[i].width = data[i].width
}
}).exec();
query.selectAll('.class-item').boundingClientRect(data => {
let dataLen = data.length;
for (let i = 0; i < dataLen; i++) {
// scroll-view 子元素组件距离上边栏的距离
this.tabbar[i].top = data[i].top;
}
}).exec()
},
upScroll(e){
if(e.detail.scrollTop>50){
this.isSticky = true
}else{
this.isSticky = false
}
console.log(e.detail.scrollTop)
},
/**
* 设置上方菜单的滚动状态
* @index 传入的 ID
*/
async tabbarStatus(index) {
this.isActive = index;
// 效果三(当前点击子元素居中展示) 不受子元素宽度影响
this.scrollLeft = this.tabbar[index].left - this.tabbarScrollW / 2 + this.tabbar[index].width / 2;
}
}
}
</script>
<style scoped>
page{
background-color: #fafafa !important;
display: block;
/* overflow: hidden; */
}
.content {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.base-combox{
align-items: center;
justify-content: center;
height: 50rpx;
}
.u-tab-item-active {
position: relative;
color: #000;
font-size: 16px;
font-weight: 600;
}
.u-tab-item-active::after {
content: ''; // 必须
display: block;
width: 30px;
height: 4px;
background-color: #000;
margin: 0 auto;
border-radius: 10px;
}
.u-tab{
border-bottom: 1rpx solid #f2f2f2;
background-color: #ffffff;
z-index: 99;
width: 100%;
align-items: center;
height: 100rpx;
margin-top: 10px;
}
.u-tab-view{
box-sizing: border-box;
padding-left: 30rpx;
padding-right: 30rpx;
width: 100%;
white-space: nowrap;
}
.u-tab-item{
display: inline-block;
box-sizing: border-box;
line-height: 60rpx;
margin-right: 35rpx;
font-size: 16px;
padding-top: 10px;
},
.u-line-1{
padding-bottom: 7px;
}
/* 隐藏scroll-view滚动条 */
/deep/::-webkit-scrollbar{
display: none;
}
uni-scroll-view .uni-scroll-view::-webkit-scrollbar {
display: none
}
.right-box{
/* height: 100vh; */
}
.page-view {
padding: 16rpx;
flex-direction: column;
}
.class-item {
display: block;
margin-bottom: 30rpx;
background-color: #fff;
padding: 16rpx;
border-radius: 8rpx;
}
.class-item:last-child {
min-height: calc(100vh - 102rpx - 50rpx - 20rpx - 88rpx - 32rpx - 64rpx );
}
.item-title {
font-size: 26rpx;
color: $u-main-color;
font-weight: bold;
}
.item-menu-name {
margin-top: 8rpx;
font-weight: normal;
font-size: 24rpx;
color: $u-main-color;
}
.item-container {
display: flex;
flex-wrap: wrap;
}
.thumb-box {
width: 25%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-top: 20rpx;
}
.item-menu-image {
width: 60rpx;
height: 60rpx;
}
.tab-sticky{
display: flex;
flex-direction: column;
position: -webkit-sticky;
position: sticky;
top: var(--window-top);
z-index: 99;
background-color: #fff;
}
</style>