切换tab时如何使选中的tab保持在中间

671 阅读2分钟

这个功能适用于有很多个tab,超出了父标签宽度或者屏幕宽度的时候,我这里用的是vue2来举例,不同语言写法有所不同,但是关键的逻辑都是一样的

最终效果图

6e3km-vbvn0.gif

关键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>