Vue实现动态滑动标签页组件:带指示器动画的Tabs
在移动端开发中,标签页切换是常见的UI模式。本文将介绍如何使用Vue和Uni-app实现一个带有动态滑动指示器的标签页组件,支持平滑过渡动画和响应式布局。
设计思路
该组件包含以下核心功能:
-
标签页切换功能
-
底部指示器随选中项动态滑动
-
平滑的过渡动画效果
-
响应式布局适应不同屏幕
完整实现代码
html
<template>
<view class="my-coupon">
<!-- 标签页导航 -->
<view class="tabs">
<view
v-for="(item, index) in tabsList"
:key="index"
class="tab-item"
:class="{'active': currentTab === index}"
@click="switchTab(index)"
:id="`tab-${index}`"
>
{{ item.name }}
</view>
<!-- 动态滑动指示器 -->
<view
class="tab-line"
:style="{
width: lineWidth,
transform: `translateX(${lineLeft})`,
transition: 'transform 0.3s ease, width 0.3s ease'
}"
/>
</view>
<!-- 内容区域 -->
<view class="content">
<view v-show="currentTab === 0" class="content-item">
<view class="coupon-card">
<text class="coupon-title">满100减20优惠券</text>
<text class="coupon-desc">适用全平台商品</text>
<text class="coupon-time">有效期至2023-12-31</text>
</view>
</view>
<view v-show="currentTab === 1" class="content-item">
<view class="used-coupon">
<text class="coupon-title">已使用的优惠券</text>
<text class="coupon-status">已使用</text>
</view>
</view>
<view v-show="currentTab === 2" class="content-item">
<view class="expired-coupon">
<text class="coupon-title">已过期的优惠券</text>
<text class="coupon-status">已过期</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentTab: 0,
tabsList: [
{ name: '待使用', type: 0 },
{ name: '已使用', type: 1 },
{ name: '已过期', type: 2 }
],
lineWidth: '60rpx', // 指示器初始宽度
lineLeft: '0px' // 指示器初始位置
}
},
mounted() {
// 组件挂载后初始化指示器位置
this.$nextTick(() => {
this.updateLinePosition()
})
},
methods: {
// 切换标签页
switchTab(index) {
this.currentTab = index
// 确保DOM更新后计算新位置
this.$nextTick(() => {
this.updateLinePosition()
})
},
// 更新指示器位置
updateLinePosition() {
const query = uni.createSelectorQuery().in(this)
query.select(`#tab-${this.currentTab}`).boundingClientRect(tabRect => {
if (tabRect) {
// 获取容器位置作为参考
query.select('.tabs').boundingClientRect(tabsRect => {
if (tabsRect) {
// 计算指示器相对于容器的位置
const relativeLeft = tabRect.left - tabsRect.left
// 计算中心偏移量,使指示器居中
const centerOffset = (tabRect.width - 30) / 2
// 更新指示器位置和宽度
this.lineLeft = `${relativeLeft + centerOffset}px`
}
}).exec()
}
}).exec()
}
}
}
</script>
<style scoped>
/* 整体容器 */
.my-coupon {
min-height: 100vh;
background: #f7f9fc;
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
}
/* 标签页容器 */
.tabs {
position: relative;
display: flex;
width: 100%;
height: 88rpx;
background: #fff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 单个标签项 */
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
color: #666;
position: relative;
z-index: 1;
transition: color 0.3s ease;
}
/* 激活状态标签 */
.tab-item.active {
color: #2B7FFE;
font-weight: 600;
}
/* 滑动指示器 */
.tab-line {
position: absolute;
bottom: 0;
height: 4rpx;
background: #2B7FFE;
border-radius: 4rpx;
z-index: 0;
}
/* 内容区域 */
.content {
padding: 30rpx;
}
/* 内容项样式 */
.content-item {
animation: fadeIn 0.4s ease;
}
/* 优惠券卡片 */
.coupon-card {
background: linear-gradient(135deg, #ffffff, #f0f7ff);
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(43, 127, 254, 0.1);
display: flex;
flex-direction: column;
border-left: 8rpx solid #2B7FFE;
margin-bottom: 30rpx;
}
.coupon-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 15rpx;
}
.coupon-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 10rpx;
}
.coupon-time {
font-size: 24rpx;
color: #999;
}
.used-coupon, .expired-coupon {
background: #f9f9f9;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
border-left: 8rpx solid #ccc;
}
.coupon-status {
font-size: 28rpx;
color: #999;
font-weight: 500;
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
关键实现解析
- 标签页结构
<view class="tabs">
<view
v-for="(item, index) in tabsList"
:key="index"
class="tab-item"
:class="{'active': currentTab === index}"
@click="switchTab(index)"
:id="`tab-${index}`"
>
{{ item.name }}
</view>
<!-- 滑动指示器 -->
<view class="tab-line"
:style="{...}"/>
</view>
- 指示器位置计算
核心方法updateLinePosition使用uni-app的createSelectorQueryAPI获取DOM位置信息:
updateLinePosition() {
const query = uni.createSelectorQuery().in(this)
query.select(`#tab-${this.currentTab}`).boundingClientRect(tabRect => {
if (tabRect) {
query.select('.tabs').boundingClientRect(tabsRect => {
if (tabsRect) {
// 计算相对位置
const relativeLeft = tabRect.left - tabsRect.left
// 居中偏移
const centerOffset = (tabRect.width - 30) / 2
// 更新指示器位置
this.lineLeft = `${relativeLeft + centerOffset}px`
}
}).exec()
}
}).exec()
}
- 平滑动画效果
通过CSS transition实现平滑过渡:
css
.tab-line {
transition: transform 0.3s ease, width 0.3s ease;
}
- 响应式设计
使用flex布局确保标签页自适应宽度:
css
.tabs {
display: flex;
width: 100%;
}
.tab-item {
flex: 1;
}
功能亮点
-
动态指示器:底部指示器精确跟随当前选中标签
-
平滑动画:切换时有流畅的过渡效果
-
响应式设计:适应不同屏幕尺寸
-
性能优化:使用transform实现硬件加速
-
视觉反馈:激活状态有明确的视觉区分
使用场景
该组件适用于:
-
优惠券管理页面
-
订单状态筛选
-
内容分类切换
-
用户信息分页展示
总结
本文实现了一个功能完善、视觉美观的标签页组件,核心在于精确计算指示器位置和流畅的动画过渡。通过uni-app的DOM查询API,我们可以在小程序环境中实现类似Web的效果。该组件具有良好的可复用性和扩展性,可以根据实际需求调整样式和功能。

浙公网安备 33010602011771号