功能需求
1、需要每次点击tab的位置滚动到第一个,同时下面页面也需要滚动
2、下面内容左右滚动时,顶部tab也需要发生改变
具体效果展示
一、实现的原理
分为2部分顶部tab标签和底部swiper
顶部tab标签需要横向滚动 scroll-view
二、直接上代码,我写注释了
<template>
<view class="container">
<!-- 顶部模板滚动 -->
<scroll-view class="cates" scroll-x="true" scroll-with-animation :scroll-left="scrollViewScrollLeft">
<view v-for="(cate, index) in cates" :key="index" class="cate-item" :class="{ 'cate-item-active': currentId === cate.id }" @click="handleCateClick(cate.id)">
{{ cate.name }}
</view>
</scroll-view>
<!-- 内容左右滑动 -->
<swiper class="content" :current="currentSwiperIndex" @change="handleSwiperChange" :style="{ height: '600px' }">
<swiper-item v-for="(cate, index) in cates" :key="index" class="swipe-content">
<scroll-view scroll-y="true" :style="{ height: '600px' }">
<view class="con-item" v-for="service in serviceList" :key="service.id">
{{ service.name }}
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
const cates = reactive([
{ id: '1', name: '模板1' },
{ id: '2', name: '模板22' },
{ id: '3', name: '模板3' },
{ id: '4', name: '模板44' },
{ id: '5', name: '模板5555' },
{ id: '6', name: '模板66' },
{ id: '7', name: '模板77' },
{ id: '8', name: '模板8' },
{ id: '9', name: '模板9999' },
// 添加更多分类...
]);
// 商品或服务列表
const serviceList = ref([{ id: '1', name: '模板1' }]);
// 当前选中的分类ID
const currentId = ref(cates[0].id);
// 存储scroll-view的滚动位置
const scrollViewScrollLeft = ref(0);
// 定义当前swiper页面的索引
const currentSwiperIndex = ref(0);
const calculateScrollPosition = async (index: number = 0) => {
// 创建一个查询器,用于收集页面中的特定节点信息。
const query = uni.createSelectorQuery();
// 通过查询器查询所有class为`cate-item`的元素,即所有分类项,并获取它们的边框大小信息。
query.selectAll('.cate-item').boundingClientRect();
// 通过查询器查询class为`cates`的元素,即滚动视图容器,并获取它的边框大小信息。
query.select('.cates').boundingClientRect();
// 执行查询,结果返回在回调函数的参数`res`中。
query.exec((res) => {
// 定义变量`totalWidthBeforeCurrent`用来记录当前选中分类项之前所有分类项的总宽度。
let totalWidthBeforeCurrent = 0;
// 遍历查询结果,res[0]包含了所有分类项的边框大小信息
res[0].forEach((item, idx) => {
// 对于当前选中分类项之前的所有分类项(即索引小于`index`的项),
// 累加它们的宽度到`totalWidthBeforeCurrent`变量。
if (idx < index) {
totalWidthBeforeCurrent += item.width;
}
});
// 根据计算出的`totalWidthBeforeCurrent`值,
// 更新滚动视图的滚动位置,使得当前选中的分类项尽可能滚动到视图区域中可见。
scrollViewScrollLeft.value = totalWidthBeforeCurrent;
});
};
// 处理分类点击
const handleCateClick = (id: string) => {
const index = cates.findIndex((cate) => cate.id === id);
currentId.value = id;
handleDataLoad();
// 更新swiper索引
currentSwiperIndex.value = index;
// 计算滚动位置
calculateScrollPosition(index);
};
// 处理swiper的change事件
const handleSwiperChange = (event: any) => {
const { current } = event.detail;
currentId.value = cates[current].id;
handleDataLoad();
// 计算滚动位置
calculateScrollPosition(current);
};
// 处理数据加载
const handleDataLoad = () => {
serviceList.value = [{ id: '1', name: `模板${currentId.value}` }];
};
onMounted(() => {
calculateScrollPosition();
});
</script>
<style>
.container {
width: 100%;
}
.cates {
white-space: nowrap;
background-color: #f5f5f5;
}
.cate-item {
display: inline-block;
padding: 10px;
color: #333;
}
.cate-item-active {
color: #9d630d;
}
.content {
margin-top: 20px;
}
.swipe-content {
text-align: center;
}
</style>
三、答疑解惑
1、为什么要写个计算方法
calculateScrollPosition 自动计算滚动的位置,这样不管我的分类名称是2个字3个字4个字不管多少个字我都可以兼容,如果定死宽度兼容不了
2、注意事项
如果你的tab会设置margin这个属性你需要注意得给他加到totalWidthBeforeCurrent上面
const calculateScrollPosition = async (index: number = 0) => {
const query = uni.createSelectorQuery();
query.selectAll('.cate-item').boundingClientRect();
query.select('.cates').boundingClientRect();
query.exec((res) => {
let totalWidthBeforeCurrent = 0;
res[0].forEach((item, idx) => {
if (idx < index) {
totalWidthBeforeCurrent += item.width;
// 如果你要给tab 每个模板设置margin属性你设置多少就需要在这里加上多少
// 加入你给每个 tab设置了 margin-right:20px
totalWidthBeforeCurrent += 20;
}
});
scrollViewScrollLeft.value = totalWidthBeforeCurrent;
});
};