1.效果图
2.原理
利用uni.createSelectorQuery获取节点信息,从而获取每组商品距离顶部的距离及tab标签的高度从而实现联动滚动
3.完整代码
<template>
<view class="wrap">
<view class="tab_box" id="tabBox">
<scroll-view scroll-x="true" :scroll-into-view="'tabs'+tabIndex">
<view class="tab_item skeleton-rect" :id="'tabs'+index" :class="{'active':tabIndex==index}"
v-for="(item,index) in tabList" @click="changeTab(index)" :key="index">
{{item.name}}
</view>
</scroll-view>
</view>
<view class="goods_class" :id="'wrap'+index" v-for="(item,index) in goodsList" :key="index">
<view class="goods_box">
<view class="title_box">
{{item.title}}
</view>
<view class="goods_list" v-if="item.good_item.length">
<view class="goods_item goods_item_inline" v-for="(i,j) in item.good_item" :key="j"
@click="$util.JumpPath(`/pages/goods/detail?id=${i.id}`)">
<view class="goods_img">
<!-- <image :src="i.slider[0]" mode="aspectFill"></image> -->
</view>
<view class="goods_title">
{{i.title}}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabIndex: 0, //选择的tab索引
tabList: [], //tab标签列表
tabHeight: 0, //tab的整体高度
goodsList: [], //商品列表
scrollWarp: [], //点击tab,页面需要滚动的距离集合
scrollInto: 0, //点击tab,页面自动滚动的距离
lock: false, //点击tab,防止与页面滚动产生冲突导致tab高亮频闪的标识
}
},
onLoad() {
this.initData() //初始化数据
},
onPageScroll(e) {
this.scrollSetTabIndex(e.scrollTop)
},
methods: {
//模拟的数据函数
initData() {
let data = []
for (let i = 0; i < 6; i++) {
data.push({
name: '测试',
id: ''
})
}
// 模拟顶部tab数据
this.tabList = data.map((item, index) => {
return {
name: item.name + index,
id: index
}
})
// 模拟商品数据
this.goodsList = this.tabList.map((item, index) => {
let goodItem = {
title: item.name,
good_item: [{
title: '商品' + item.name
}, {
title: '商品' + item.name
}, {
title: '商品' + item.name
}, {
title: '商品' + item.name
}]
}
return goodItem
})
this.$nextTick(()=>{
this.GetRect("#tabBox").then(res => {
this.tabHeight = res.height
this.barInit()
console.log(this.tabHeight,"tab高度");
})
})
},
changeTab(e) {
this.tabIndex = e
this.$set(this, 'lock', true);
this.scrollInto = this.scrollWarp[e]
this.scrollTo()
},
// 滚动自动激活tab标签
scrollSetTabIndex(top) {
if (this.lock) {
// 防止点击tab标签时和页面滚动造成冲突导致tab标签的选中状态会不停闪烁
setTimeout(() => {
this.$set(this, 'lock', false);
}, 700)
return;
}
if (top <= this.scrollWarp[0]) {
this.tabIndex = 0
} else if (top >= this.scrollWarp[this.scrollWarp.length - 1]) {
this.tabIndex = this.scrollWarp.length - 1
} else {
for (let i = 0; i < this.scrollWarp.length - 1; i++) {
if (top >= this.scrollWarp[i] && top < this.scrollWarp[i + 1]) {
this.tabIndex = i
}
}
}
},
scrollTo() {
uni.pageScrollTo({
scrollTop: this.scrollInto
})
},
//获取节点信息
GetRect(selector) {
return new Promise((resolve, reject) => {
let view = uni.createSelectorQuery().in(this)
view.select(selector).boundingClientRect(rect => {
resolve(rect)
}).exec();
})
},
//获取节点距离顶部距离
barInit: async function(index) {
let navTargetTop1 = [];
let randoms = this.tabHeight; //顶部栏高度+tab高度+兼容值 (注,如果navbar是自定义的,还需要获取navbar的高度并减去此高度)
console.log(randoms,"--------------------");
for (let i = 0; i < this.goodsList.length; i++) {
this.GetRect("#wrap" + i).then(res => {
navTargetTop1.push(parseInt(res.top) - randoms) //滚动的距离:元素距离顶部的距离-tab标签的高度
console.log(res.top,randoms,"----------");
})
}
this.scrollWarp = navTargetTop1;
console.log(this.scrollWarp, "需要滚动的合集");
},
},
}
</script>
<style lang="scss" scoped>
.goods_class {
margin-bottom: 50rpx;
.goods_box {
margin-bottom: 50rpx;
padding: 0 20rpx;
.title_box {
font-size: 40rpx;
font-weight: 600;
padding-top: 10rpx;
margin-bottom: 20rpx;
}
.goods_list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
.goods_item_out {
width: 270rpx;
display: inline-block;
vertical-align: top;
}
.scroll-view_H {
white-space: nowrap;
width: 100%;
}
.goods_item_inline {
display: inline-block;
}
.goods_item {
// width: 270rpx;
overflow: hidden;
vertical-align: top;
.goods_img {
width: 100%;
height: 270rpx;
background: #303030;
image {
width: 100%;
height: 100%;
}
}
.goods_title {
margin: 10rpx 0;
white-space: normal;
width: 100%;
max-height: 76rpx;
line-height: 38rpx;
}
.goods_title,
.goods_price {
font-size: 26rpx;
color: #303030;
}
}
}
}
.more_btn {
width: 252rpx;
height: 82rpx;
margin: 0 auto;
background: var(--view-theme);
.in_btn {
width: 236rpx;
height: 70rpx;
line-height: 70rpx;
border: 1px solid #FFFFFF;
text-align: center;
font-size: 28rpx;
color: #fff;
}
}
.scroll_box {
position: relative;
width: 250rpx;
height: 3rpx;
background: #CCCCCC;
margin: 0 auto 37rpx;
.scroll_icon {
/* 初始位置 */
transform: translateX(0px);
width: 60rpx;
height: 3rpx;
background: #383838;
}
}
}
.tab_box {
position: sticky;
z-index: 999;
top: 0; //动态设置
background: #fff;
padding: 0 35rpx;
// height: 80rpx;
line-height: 80rpx;
// margin-bottom: 20rpx;
padding-bottom: 20rpx;
white-space: nowrap;
.tab_item {
display: inline-block;
position: relative;
height: 100%;
font-size: 34rpx;
color: #303030;
&:not(:last-child) {
margin-right: 40rpx;
}
}
.active {
font-weight: 700;
color: red;
}
.active::after {
position: absolute;
left: 0;
bottom: 0;
content: '';
width: 100%;
height: 2rpx;
background: red;
}
}
</style>