一步一步使用Vue写一个简单的顶部Tabbar
2019-08-11 本文已影响0人
流水_事
最近用Hybird写一些简单的需求。其中有一个要用到顶部选项卡的控件。在github上并没有找到合适的,要么过于复杂,要么样式不合适。于是决定自己写一个简单的顶部选项卡。这篇文章是分享做这个tabbar的过程以及其中遇到的一些问题。
使用到的知识点
vue 的组件,绑定,事件传递,css的动态绑定
先来看我们要做成的原页面(原生iOS做的):

要做的组件就是顶部的tabbar这一块。
分析
其实就是要放n个tab view布满整个宽度,然后底部要一个滑动条可以动画滚动。
关键问题点:
- tabs数组是作为属性传递过来的,如果按照百分比来定义宽度,如何动态地把css中的宽度跟传递的data做绑定?
- 点击tabbar后,如何让底部的滑动条滚动到合适的位置?
步骤
由于对vue和css还不是特别熟悉,这里采取分步的方式一点一点达到最终的效果。
-
将整个tabbar作为一个view而不是一个组件。先做好整个view再抽取一个单独的组件出来。 这样做相对于一开始就把它当做一个组件来说,少了引用与传值的部分,对于初学不久的人来说最好是控制变量,保证问题集中在自己关注的地方。
-
写死数据,先固定宽度25%写死,先把整体的界面先完成。完成简单的数据绑定,避开第一个难点。
此时的代码
<template>
<view class="container">
<view class="tab-container">
<view class="tabs">
<view class="tab-item"
v-for="(item,index) in tabNames"
style="width:25%">
<text class="tab-label">{{item}}</text>
</view>
</view>
<view class="slider" ref="slider"></view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabNames: ["代办","已办","待阅","已阅"]
}
}
}
</script>
- 开始研究第一个问题,如何动态地绑定tab的宽度,使其能够刚好布满整个屏幕的宽度。没有太多的技术深度,这里直接贴出来:
:style="{width: (100/tabNames.length) + '%'}"
这里主要注意一下语法的问题就可以了。
- 绑定点击事件,处理底部滑块的位置。在这里因为对css动画的不熟悉,我也分了两步。第一,先达到没有动画移动滑动位置的效果,只需要修改margin-left属性就可以了。但是需要在js的事件函数中去获取滑块的css属性来修改:
let style = this.$refs.slider.$el.style;
let offset = 25*index + '%';
style['margin-left'] = offset;
- 达到效果以后,改为用动画让滑块平滑地移动。查阅css动画即可
let style = this.$refs.slider.$el.style;
let offset = 100*index + '%';
//字符串模板的使用 ${}
style['transform'] = `translate(${offset})`;
这里要注意的点是元素在translate百分比移动的时候,是已自身的宽度为单位的,所以要把offset的基准定为100%
- 开始抽离tabbar作为一个单独的控件,这时候要考虑作为一个组件,有哪些地方是应该由外部传入而作为属性存在的。
由于是个简单的demo,这里只把最基础的配置抽出来:tabs的数据源,是否显示底部的滑块,点击事件的传递($emit)
此时组件的代码如下:
<template>
<view class="tab-container">
<view class="tabs">
<view
class="tab-item"
v-for="(item,index) in tabNames"
:style="{width: (100/tabNames.length) + '%'}"
@click="onTabClick(index)">
<text
class="tab-label"
:class="selectedIndex===index?'selected':''"
>{{item}}</text>
</view>
</view>
<view class="slider" ref="slider" v-if="showSlider"></view>
</view>
</template>
<script>
export default {
name: "top-tab-bar",
props: {
tabNames: Array,
showSlider: Boolean
},
data() {
return {
selectedIndex: 0
}
},
methods: {
onTabClick(index) {
// 获取slider的style
this.$data.selectedIndex = index;
let slider = this.$refs.slider;
if (slider !== undefined) {
let style = slider.$el.style;
let offset = 100*index + '%';
//字符串模板的使用 ${}
style['transform'] = `translate(${offset})`;
}
this.$emit('onTabClick',index);
}
}
}
</script>
这里遇到的一个小坑就是在绑定的时候如果用到v-for里面的item时,需要将绑定的代码写在在v-for以后(也就是item定义以后)
点击的tab的样式的绑定:
:class="selectedIndex===index?'selected':''"
- 在父组件中使用:
<template>
<view class="container">
<top-tab-bar
:tabNames="tabNames"
:showSlider="true"
@onTabClick="onTabClick">
</top-tab-bar>
</view>
</template>
<script>
import TopTabBar from "./top-tab-bar.vue"
export default {
components: {
TopTabBar
},
data() {
return {
tabNames: ["代办","已办","待阅","已阅"]
}
},
methods: {
onTabClick(index) {
console.log("parent receive on tab click! ");
}
}
}
</script>
- 最后。提供完整的使用。通过tabbar切换显示的数据源
<template>
<view class="container">
<top-tab-bar
:tabNames="tabNames"
:showSlider="true"
@onTabClick="onTabClick">
</top-tab-bar>
<message-list class="list" :messages="currentMessages"></message-list>
</view>
</template>
<script>
import TopTabBar from "./top-tab-bar.vue"
import messageData from "./message-data.js"
import MessageList from "./message-list.vue"
export default {
components: {
TopTabBar,
MessageList
},
data() {
return {
tabNames: ["代办","已办","待阅","已阅"],
messages: [messageData.todo, messageData.done, messageData.unread, messageData.readed],
currentMessages: messageData.todo
}
},
methods: {
onTabClick(index) {
console.log("parent receive on tab click! ");
this.$data.currentMessages = this.$data.messages[index];
}
}
}
</script>
最终效果 具体的小样式就没有详细完善了

接下来的目标:
- 将tab从tabbar中抽离出来,单独定义tab的样式,可以有图片或者角标
- tabbar可以滑动,类似头条的顶部分类tabbar
- 和scrollview相结合,tabbar提供随着底部页面的滑动而滑动的功能