uniapp vue3 自定义tabs滚动,点击子项居中

93 阅读1分钟
<template>
    <view class="tabs-list">
        <scroll-view
            ref="scrollRef"
            class="scroll-wrap" 
            scroll-x="true"
            :scroll-left="scrollLeft"
            >
            <view 
                class="tabs-item"
                v-for="(d,i) in list"
                :key="i"
                :class="{active: i === innerCurrent }"
                @click="tansItemClick(d,i)"
                >
                <view class="tabs-item-box">
                    <view class="name">
                        {{ d.name }}
                    </view>
                </view>
            </view>
        </scroll-view>
    </view>
</template>

<script>
    export default {
        name:"m-tabs-scroll"
    }
</script>

<script setup>
    import { defineProps, ref, defineEmits, getCurrentInstance, watch, nextTick, onMounted} from 'vue';
	
    const props = defineProps({
        list: {
            type: Array,
            default: () => []
        },
        // 选中索引
        current: {
            type: Number,
            default: 0
        }
    })

    const emits = defineEmits(['change', 'click']);

    const instance = getCurrentInstance();

    // tabs 选中下标
    const innerCurrent = ref(0);

    // scroll-view 节点布局区域的边界
    const tabsRect = ref({})

    // 存储每个标签项节点布局区域的边界
    const tabRect = ref([]); 
    // 需要设置的滚动位置
    const scrollLeft = ref(0);

    // scroll-view 滚动宽度
    const scrollViewWidth = ref(0);
	
	
    watch(() => props.list, () => {
        nextTick(() => {
            resize();
        })
    },{
        immediate: true
    }
    )
	
    watch(() => props.current, (newValue) => {
        if (newValue !== innerCurrent.value) {
            innerCurrent.value = newValue
            nextTick(() => {
                resize()
            })
        }
    },{
        immediate: true
    }
    )
	
    function resize() {
        // 如果不存在list,则不处理
        if(props.list.length === 0) {
            return
        }
        Promise.all([setTabsRect(), setTabRect()]).then(() => {
            setScrollLeft()
        })
    }
	
    function setTabRect() {
        return new Promise(resolve => {
            const query = uni.createSelectorQuery().in(instance.proxy);
            // 计算并设置每个标签项的宽度
            query.selectAll('.tabs-item').boundingClientRect((rects) => {
                scrollViewWidth.value = 0;
                tabRect.value = rects.map(rect => {
                    scrollViewWidth.value += rect.width;
                    return rect
                });
                resolve();
            }).exec();
        })
    }
	function setTabsRect() {
            return new Promise(resolve => {
                const query = uni.createSelectorQuery().in(instance.proxy);
                query.select('.scroll-wrap').boundingClientRect((rect) => {
                    tabsRect.value = rect;
                    resolve()
                }).exec();
            })
	}
	
	
	function tansItemClick(data, index) {
            emits('click', {
                index,
                data
            })
            if(innerCurrent.value !== index) {
                emits('change', {
                    index,
                    data
                })
            }
            innerCurrent.value = index;
            setScrollLeft()
	}
	
	function setScrollLeft() {
		
            // 累加得到当前item到左边的距离
            const offsetLeft = tabRect.value
                .slice(0, innerCurrent.value)
                .reduce((total, curr) => {
                    return total + curr.width;
                }, 0)
            // 屏幕宽度
            const windowWidth = uni.getWindowInfo().windowWidth;
            // scroll-view 宽度
            const tabsRectWidth = windowWidth - (tabsRect.value.left || 0) - (tabsRect.value.right || 0);
            const item = tabRect.value[innerCurrent.value];
            scrollLeft.value = item.left - tabsRectWidth / 2 + item.width / 2;
	}
	
</script>

<style scoped lang="scss">
    .tabs-list {
        margin-top: 40rpx;
        .scroll-wrap {
            white-space: nowrap;
            height: 120rpx;
        }
        .tabs-item {
            display: inline-block;
            min-width: 180rpx;
            height: 76rpx;
            padding: 0rpx 10rpx;
            box-sizing: border-box;

            &.active {
                .tabs-item-box {
                    background-color: #418BFE;
                    color: #fff;
                    &::after {
                        content: ' ';
                        position: absolute;
                        left: 50%;
                        transform: translateX(-50%);
                        bottom: -14rpx;
                        width: 14rpx;
                        height: 14rpx;
                        background-size: 100% 100%;
                        // background-image: url('~@/static/icon4.png');
                    }
                }
            }

            .tabs-item-box {
                background: #F8FCFE;
                border-radius: 10rpx;
                position: relative;
                color: #323232;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                height: 100%;
                .name {
                    font-family: PingFang SC;
                    font-weight: 500;
                    font-size: 28rpx;
                }
            }
        }
    }
	
</style>