vue
复制代码
<template>
<div class="my-tab" ref="myTabRef">
<div
class="my-tab-item"
v-for="(item, index) in tabs"
:ref="(el) => (itemRefs[index] = el)"
:class="{ active: activeName === item.name }"
@click="tabClick(item)"
>
<span class="tab-text">{{ item.label }}</span>
</div>
<div class="slider" :style="sliderStyle"></div>
</div>
</template>
<script setup>
import { ref, computed, nextTick, onMounted, watch } from 'vue'
const props = defineProps({
tabs: {
type: Array,
required: true,
default: () => [],
},
defaultActive: {
type: String,
default: '',
},
})
const emit = defineEmits(['change'])
const myTabRef = ref(null)
const itemRefs = ref([])
const activeName = ref('')
watch(
() => props.defaultActive,
(val) => {
if (val) activeName.value = val
},
{ immediate: true }
)
onMounted(() => {
if (!activeName.value && props.tabs.length) {
activeName.value = props.tabs[0].name
}
nextTick(() => scrollToCenter())
})
const tabClick = async (item) => {
activeName.value = item.name
emit('change', item)
await nextTick()
scrollToCenter()
}
const scrollToCenter = () => {
const nav = myTabRef.value
if (!nav) return
const index = props.tabs.findIndex((t) => t.name === activeName.value)
const item = itemRefs.value[index]
if (!item) return
const to = item.offsetLeft - (nav.offsetWidth - item.offsetWidth) / 2
nav.scrollLeft = to
}
const sliderStyle = computed(() => {
const index = props.tabs.findIndex((t) => t.name === activeName.value)
const el = itemRefs.value[index]
if (!el) return { opacity: 0 }
return {
left: el.offsetLeft + 'px',
width: el.offsetWidth + 'px',
opacity: 1,
}
})
</script>
<style lang="less" scoped>
.my-tab {
position: relative;
height: 44px;
background: #fff;
display: flex;
align-items: center;
overflow-x: auto;
white-space: nowrap;
scroll-behavior: smooth;
padding: 0 10px;
&::-webkit-scrollbar {
width: 0;
height: 0;
display: none;
}
}
.my-tab-item {
flex: none;
height: 44px;
line-height: 44px;
padding: 0 14px;
color: #999;
font-size: 14px;
position: relative;
z-index: 1;
cursor: pointer;
&.active {
color: #333;
font-weight: bold;
}
}
.tab-text {
display: inline-block;
}
.slider {
position: absolute;
left: 0;
bottom: 0;
height: 3px;
background: linear-gradient(180deg, #649afc 0%, #5180f4 100%);
border-radius: 2px;
opacity: 0;
transition: all 0.3s ease;
z-index: 0;
}
.slider::before{
content: '';
position: absolute;
left: 0;
bottom: 50%;
height: 50%;
background: #649afc;
border-radius: 50px;
opacity: 0;
transition: all 0.3s ease;
z-index: 0;
}
</style>
```
## 父组件
```vue
<template>
<div style="width: 500px; margin: 20px;">
<SliderTabs
:tabs="list"
default-active="tabThree"
@change="handleChange"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import SliderTabs from './ZiZuJian.vue'
// 父组件传递列表
const list = ref([
{ name: 'tabOne', label: 'tab切换显示1' },
{ name: 'tabTwo', label: 'tab切换显示2' },
{ name: 'tabThree', label: 'tab切换显示3' },
{ name: 'tabFour', label: 'tab切换显示4' },
{ name: 'tabFive', label: 'tab切换显示5' },
{ name: 'tabSix', label: 'tab切换显示6' },
{ name: 'tabSeven', label: 'tab切换显示7' },
{ name: 'tabEight', label: 'tab切换显示8' },
{ name: 'tabNine', label: 'tab切换显示9' },
{ name: 'tabTen', label: 'tab切换显示10' },
{ name: 'tabEleven', label: 'tab切换显示11' },
{ name: 'tabTwelve', label: 'tab切换显示12' },
{ name: 'tabThirteen', label: 'tab切换显示13' },
{ name: 'tabFourteen', label: 'tab切换显示14' },
{ name: 'tabFifteen', label: 'tab切换显示15' },
])
// 切换事件
const handleChange = (index) => {
console.log('当前选中索引:', index)
}
</script>
```