实现带滑动效果的 Tab 切换栏的示例,类似 ant-design 的 Segmented 组件效果:
<!-- TabSegmented.vue -->
<template>
<div class="tab-segmented" ref="tabContainer">
<!-- 选项容器 -->
<div class="tab-wrapper">
<div
v-for="(item, index) in options"
:key="index"
class="tab-item"
:class="{ active: modelValue === item.value }"
@click="handleSelect(item)"
ref="tabItems"
>
{{ item.label }}
</div>
<!-- 滑块 -->
<div
class="slider"
:style="{
width: sliderWidth + 'px',
transform: `translateX(${sliderOffset}px)`,
}"
></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
interface Option {
label: string
value: string | number
}
const props = defineProps<{
options: Option[]
modelValue: string | number
}>()
const emit = defineEmits(['update:modelValue'])
const tabContainer = ref<HTMLElement | null>(null)
const tabItems = ref<HTMLElement[]>([])
const sliderWidth = ref(0)
const sliderOffset = ref(0)
// 更新滑块位置和宽度
const updateSlider = (index: number) => {
if (tabItems.value[index]) {
const currentTab = tabItems.value[index]
sliderWidth.value = currentTab.offsetWidth
sliderOffset.value = currentTab.offsetLeft
}
}
// 处理选择
const handleSelect = (item: Option) => {
emit('update:modelValue', item.value)
}
// 监听选中值变化
watch(
() => props.modelValue,
() => {
const index = props.options.findIndex(item => item.value === props.modelValue)
if (index !== -1) {
updateSlider(index)
}
}
)
// 组件挂载后初始化滑块位置
onMounted(() => {
const index = props.options.findIndex(item => item.value === props.modelValue)
if (index !== -1) {
updateSlider(index)
}
})
</script>
<style scoped>
.tab-segmented {
width: 100%;
padding: 4px;
background-color: #f0f0f0;
border-radius: 6px;
}
.tab-wrapper {
position: relative;
display: flex;
width: 100%;
}
.tab-item {
flex: 1;
padding: 6px 12px;
text-align: center;
cursor: pointer;
transition: color 0.3s;
position: relative;
z-index: 1;
user-select: none;
}
.tab-item.active {
color: #1890ff;
}
.slider {
position: absolute;
height: 100%;
background-color: #fff;
border-radius: 4px;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
top: 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
</style>
使用示例:
<!-- App.vue -->
<template>
<div class="container">
<TabSegmented
v-model="activeTab"
:options="options"
/>
<!-- 展示当前选中的值 -->
<div class="content">
当前选中: {{ activeTab }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabSegmented from './components/TabSegmented.vue'
const activeTab = ref('1')
const options = [
{ label: '选项一', value: '1' },
{ label: '选项二', value: '2' },
{ label: '选项三', value: '3' },
{ label: '选项四', value: '4' },
]
</script>
<style scoped>
.container {
max-width: 600px;
margin: 100px auto;
padding: 20px;
}
.content {
margin-top: 20px;
text-align: center;
}
</style>
这个实现包含以下特性:
- 支持 v-model 双向绑定
- 平滑的滑块过渡动画
- 响应式布局
- TypeScript 类型支持
- 可自定义选项数量
如果你想要添加更多功能,可以考虑以下扩展:
- 添加禁用状态:
<template>
<div
class="tab-item"
:class="{
active: modelValue === item.value,
disabled: item.disabled
}"
@click="!item.disabled && handleSelect(item)"
>
{{ item.label }}
</div>
</template>
<style>
.tab-item.disabled {
cursor: not-allowed;
opacity: 0.5;
}
</style>
- 添加图标支持:
<template>
<div class="tab-item">
<i v-if="item.icon" :class="item.icon"></i>
<span>{{ item.label }}</span>
</div>
</template>
- 添加自定义颜色支持:
<script setup lang="ts">
const props = defineProps<{
options: Option[]
modelValue: string | number
activeColor?: string
backgroundColor?: string
}>()
</script>
<template>
<div
class="tab-segmented"
:style="{
backgroundColor: backgroundColor
}"
>
<!-- ... -->
<div
class="tab-item"
:style="{
color: modelValue === item.value ? activeColor : ''
}"
>
{{ item.label }}
</div>
</div>
</template>
- 添加尺寸支持:
<script setup lang="ts">
const props = defineProps<{
size?: 'small' | 'medium' | 'large'
}>()
</script>
<style scoped>
.tab-segmented.small { padding: 2px; }
.tab-segmented.medium { padding: 4px; }
.tab-segmented.large { padding: 6px; }
.tab-item.small { padding: 4px 8px; }
.tab-item.medium { padding: 6px 12px; }
.tab-item.large { padding: 8px 16px; }
</style>
- 添加动画效果:
.tab-item {
transition: all 0.3s;
}
.tab-item:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.slider {
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
这些扩展可以根据实际需求选择性添加。组件的核心是通过监听选中值的变化来更新滑块的位置,并使用 CSS transition 实现平滑的过渡效果。