一、效果图

切换 Tabs 标签时,蓝条长度和位置有过渡效果
二、实现思路
<g-tabs :selected-tab.sync="selectedTab" direction="column">
<g-tabs-nav>
<template v-slot:items>
<g-tabs-items tagName="vehicle">vehicle</g-tabs-items>
<g-tabs-items tagName="girl">girl</g-tabs-items>
<g-tabs-items tagName="sports">sports</g-tabs-items>
</template>
</g-tabs-nav>
</g-tabs>切换时需要拿到 items 的宽度和位置,这样的话就必须通过传递被选中 items 实例来获取。用到的API:
- Element.clientWidth属性返回元素节点的 CSS 宽度,只对块级元素有效,也是只包括元素本身的宽度和padding
- Element.offsetLeft返回当前元素左上角相对于Element.offsetParent节点的水平位移
- Element.offsetParent属性返回最靠近当前元素的、并且 CSS 的position属性不等于static的上层元素。
蓝条代码有以下几种选择
- 写在g-tabs-nav
- 写在g-tabs-items
- 单独一个组件
无论写在哪里重点就在于拿到被选中 items 的实例,如果直接把核心代码写在 nav 里,那么必须借助eventBus,如果写在 g-tabs-items 里很难实现左右移动的效果但是宽度的动画很容易实现
三、实现代码
那么我们选择单独的一个组件,用 props 来接受 items 实例,相性很好
//g-tabs-bar.vue
<template>
<div class="tab-bar" :style="barStyle"></div>
</template>
<script>
export default {
name: "yibo-tab-bar",
props: ["tabs"],
computed: {
barStyle: {
get() {
let barWidth = 0;
let style = {};
if (this.tabs && this.tabs.active) {
barWidth = this.tabs.$el.clientWidth;
let transformAfter = this.tabs.$el.offsetLeft;
style.width = barWidth + "px";
style.left = transformAfter + "px";
}
return style;
}
}
}
};
</script>
<style lang="scss" scoped>
$blue: #1890ff;
.tab-bar {
border-bottom: 1px solid $blue;
position: absolute;
bottom: 0;
left: 0;
transition: left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
</style>关键就在于 props: ["tabs"] 怎么传进来
//g-tabs-items.vue
watch:{
active:function(val){
if (val) {
this.eventBus.$emit("activeVm", this);
}
}
}//g-tabs-nav.vue
<g-tab-bar :tabs="activeVm" ></g-tab-bar>
data() {
return {
activeVm: null
};
},
components: {
"g-tab-bar": tabsBar
},
mounted() {
this.eventBus.$on("activeVm", this.passingThis);
},
methods: {
passingThis(activeVm) {
this.activeVm = activeVm;
}
}实现了好久咋写出来就这么点内容呢