这个功能适用于有很多个tab,超出了父标签宽度或者屏幕宽度的时候,我这里用的是vue2来举例,不同语言写法有所不同,但是关键的逻辑都是一样的
最终效果图
关键html
在scroll-view中要加一个ref引用指向DOM元素以备后面获取tab的左偏移量和元素宽度
<scroll-view scroll-x="true" :scroll-left="scrollLeft" ref="scrollContainer" class="tabScroll">
<view v-for="(tab, index) in tabs" :key="index"
:class="'li ' + (current === index ? 'selected' : '')" :data-current="index"
@tap="tabchange(index)">
{{ tab }}
</view>
</scroll-view>
关键js
计算tab位置的事件
scrollToCenter(index) {
let _this = this;
// 获取每个 tab 的宽度
let tabItem = this.$refs.scrollContainer.$children[index];
if (tabItem) { // 只有在 tab 存在的情况下才进行计算
const tabWidth = tabItem.$el.offsetWidth;
const containerWidth = this.$refs.scrollContainer.$el.offsetWidth;
const itemOffsetLeft = tabItem.$el.offsetLeft;
const halfItemWidth = tabWidth / 2;
const halfScreenWidth = containerWidth / 2;
this.scrollLeft = itemOffsetLeft - (halfScreenWidth - halfItemWidth)
}
}
这里取到上面准备好的引用scrollContainer,containerWidth是整个tabbar的宽度,tabWidth是选中的tab的宽度,itemOffsetLeft是选中tab的当前位置(从第一个tab的起点到选中tab的起点的距离),我们想要的距离是整个tabbar宽度的一半 - 选中的tab宽度的一半,那么使之保持在中间的表达式就是halfScreenWidth - halfItemWidth,然后用itemOffsetLeft - (halfScreenWidth - halfItemWidth)把滚动条移到中间,赋值给scrollLeft
切换事件
//tab切换
tabchange: function(index) {
this.current = index
},
//滑动事件
eventchange: function(event) {
this.current = event.detail.current;
this.scrollToCenter(this.current);
},
之所以HTML点击事件调用的是点击切换tabchange (@tap="tabchange(index)")而js中将scrollToCenter写在滑动切换eventchange里,是因为这里的组件swiper当点击切换时会同时触发滑动切换,而滑动切换不会同时触发点击切换
代码整体
<template>
<view class="tabs" id="tabs">
<view class="tabsNav">
<scroll-view scroll-x="true" :scroll-left="scrollLeft" ref="scrollContainer" class="tabScroll">
<view v-for="(tab, index) in tabs" :key="index"
:class="'li ' + (current === index ? 'selected' : '')" :data-current="index"
@tap="tabchange(index)">
{{ tab }}
</view>
</scroll-view>
</view>
<swiper class="tabsBox" :current="current" @change="eventchange">
<swiper-item class="pages normal-sign-box">
1
</swiper-item>
<swiper-item class="pages normal-sign-box">
2
</swiper-item>
<swiper-item class="pages normal-sign-box">
3
</swiper-item>
<swiper-item class="pages normal-sign-box">
4
</swiper-item>
<swiper-item class="pages normal-sign-box">
5
</swiper-item>
<swiper-item class="pages normal-sign-box">
6
</swiper-item>
</swiper>
</view>
</template>
<script lang="js">
export default {
data() {
return {
current: 0,
scrollLeft: 0,
tabs: [
'111',
'222',
'333',
'444444',
'555',
'666'
]
}
},
methods: {
//tab切换
tabchange: function(index) {
this.current = index
},
//滑动事件
eventchange: function(event) {
this.current = event.detail.current;
this.scrollToCenter(this.current);
},
scrollToCenter(index) {
let _this = this;
// 获取每个 tab 的宽度
let tabItem = this.$refs.scrollContainer.$children[index];
if (tabItem) { // 只有在 tab 存在的情况下才进行计算
const tabWidth = tabItem.$el.offsetWidth;
const containerWidth = this.$refs.scrollContainer.$el.offsetWidth;
const itemOffsetLeft = tabItem.$el.offsetLeft;
const halfItemWidth = tabWidth / 2;
const halfScreenWidth = containerWidth / 2;
this.scrollLeft = itemOffsetLeft - (halfScreenWidth - halfItemWidth);
}
}
}
}
</script>
<style lang="scss" scoped>
.tabs {
height: calc(100vh - 168rpx);
.tabsNav {
clear: both;
height: 36px;
line-height: 36px;
width: 100%;
border-bottom: #d2d2d2 1px solid;
background-color: #f8f8f8;
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 9;
overflow: auto;
width: calc(100vw - 33px);
.tabScroll {
white-space: nowrap;
display: block;
width: 100%;
.li {
text-align: center;
list-style: none;
cursor: pointer;
font-weight: 400;
font-size: 14px;
color: #999;
padding: 0 10px;
width: -webkit-max-content;
width: max-content;
display: inline-block;
min-width: 90px;
}
}
}
.tabsBox {
height: calc(100% - 80rpx);
}
}
</style>