在前端开发中,TabBar 是一个非常常见的 UI 组件,而在一些设计需求中,我们希望激活的 Tab 有动态变化的圆角效果,并且宽度可以根据选中状态自适应。本文将分享一个 Vue3 + Less 实现的 TabBar 组件案例,完整分析代码结构、实现思路,并展示优化技巧。
效果预览:
🧩 需求分析
我们希望 TabBar 拥有以下特性:
- 动态激活状态:根据
modelValue高亮当前 Tab。 - 圆角过渡效果:激活 Tab 的上下圆角动态显示。
- 自适应宽度:选中 Tab 宽度放大,未选中 Tab 等分。
- 复用性强:多种激活形态(左圆角、右圆角、左右圆角)可复用样式。
- 响应用户点击:选中状态更新并触发事件。
🛠 组件实现
1️⃣ 模板部分
<div class="tabbar" :style="{
'grid-template-columns': getGridFr
}">
<div
:class="{
'tabbar-item': true,
'active': modelValue === it.value,
'active3': index > 0 && index < tabs.length - 1,
'active1': index === 0,
'active2': index === tabs.length - 1,
}"
v-for="(it, index) in tabs"
@click="emit('update:modelValue', it.value)"
>
<div class="top"></div>
<div style="display: flex;align-items: center">
<img style="width: 20px;margin-right: 5px;" :src="it.icon" alt="">
{{ it.label }}
</div>
</div>
</div>
- 动态类绑定:根据不同 Tab 值,使用
active1/active2/active3控制圆角样式。 - 网格布局:
grid-template-columns根据选中状态动态调整宽度。
样式部分(Less )
.tabbar {
display: grid;
transition: all 0.3s;
--th: 8px;
&-item {
text-align: center;
height: 40px;
background-color: var(--el-color-primary);
color: #fff;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
}
// ----------- 公共激活样式 -----------
.active-base() {
color: var(--el-color-primary);
background-color: #fff;
position: relative;
--bh: 40px;
.top {
position: absolute;
height: var(--th);
background-color: #fff;
top: calc(var(--th) * -1);
}
}
// 圆角片段(左右通用)
.circle(@pos, @clip) {
display: block;
content: '';
position: absolute;
width: var(--bh);
height: var(--bh);
background-color: var(--el-color-primary);
@{pos}: 0;
bottom: 0;
z-index: 1;
clip-path: @clip;
}
// ----------- 各种激活样式 -----------
// 左右都有圆角
.active {
.active-base();
.top {
border-radius: 100px 100px 20px 20px;
width: calc(100% - 80px);
}
&::before { .circle(left, ellipse(100% 100% at 0% 0%)); }
&::after { .circle(right, ellipse(100% 100% at 100% 0%)); }
}
// 右侧圆角
.active1 {
.active-base();
.top {
border-radius: 0 100px 20px 20px;
width: calc(100% - 40px);
left: 0;
}
&::after { .circle(right, ellipse(100% 100% at 100% 0%)); }
}
// 左侧圆角
.active2 {
.active-base();
.top {
border-radius: 100px 0 20px 20px;
width: calc(100% - 40px);
right: 0;
}
&::before { .circle(left, ellipse(100% 100% at 0% 0%)); }
}
}