⼩程序⾃定义tabbar
⼀定的需求情况下,⽆法使⽤⼩程序原⽣的 tabbar 的时候,需要⾃⾏实现⼀个和 tabbar 功能⼀模⼀样的⾃制组件。
查阅了海量的博客和⽂档之后,亲⾃踩坑。总结了三种在不使⽤⼩程序原⽣ tabbar的情况下⾃制 tabbar 的⽅法。并说说这⼏种⽅法各⾃的特⾊。
类 navigator 跳转⽅式
类 navigator 跳转⽅式是我⾃⼰起的名称,因为它的实现思路就是这个样⼦的。期初参考这篇博⽂的思路。进⾏了这种⽅式的尝试,并为后续提供了解决思路。在这次实践的过程中使⽤了和该博⽂类似的⽬录结构。
template ⽂件主要包含了 tabbar 的内容、逻辑、模板、样式。
tabbar_template.js
//初始化数据
navigator标签function tabbarinit() {
return [
{
"current": 0,
"pagePath": "/pages/travel_shop/travel/travel_index/travel_index",
"iconPath": "/pages/img/tab_icon_home@2x.png",
"selectedIconPath": "/pages/img/tab_icon_home_sel@2x.png",
"text": "⾸页"
},
{
"current": 0,
"pagePath": "/pages/travel_shop/travel/travel_car/travel_car",
"iconPath": "/pages/img/tab_icon_shop@2x.png",
"selectedIconPath": "/pages/img/tab_icon_shop_sel@2x.png",
"text": "购物车"
},
{
"current": 0,
"pagePath": "/pages/travel_shop/travel/travel_my/travel_my",
"iconPath": "/pages/img/tab_icon_my@2x.png",
"selectedIconPath": "/pages/img/tab_icon_my_sel@2x.png",
"text": "我的"
}
]
}
//tabbar 主⼊⼝
function tabbarmain(bindName = "tabdata", id, target) {
var that = target;
var bindData = {};
var otabbar = tabbarinit();
otabbar[id]['iconPath'] = otabbar[id]['selectedIconPath']  //换当前的icon
otabbar[id]['current'] = 1;
bindData[bindName] = otabbar
that.setData({ bindData });
}
tabbar: tabbarmain
}
tabbar_template.wxml
<template name="tabBar">
<view class="tabBar">
<block wx:for="{{tabBar}}" wx:for-item="item" wx:key="tabBar">
<view class="tabBar-item">
<navigator open-type="reLaunch" url="{{item.pagePath}}">
<view><image class="tabBar-icon" src='{{item.iconPath}}'></image></view>
<view class="{{item.current== 1 ? 'tabBartext' :''}}">{{}}</view>
</navigator>
</view>
</block>
</view>
</template>
tabbar_template.wxss
.tabBar-icon{
width:54rpx;
height: 54rpx;
}
.tabBar{
position: fixed;
bottom:0;
padding:10rpx;
margin-left:-4rpx;
background:#F7F7FA;
font-size:24rpx;
color:#8A8A8A;
box-shadow: 3rpx 3rpx 3rpx 3rpx #aaa;
z-index: 9999;
}
.tabBar-item{
float:left;
width: 33.333%;
text-align: center;
overflow: hidden;
}
/*当前字体颜⾊*/
.tabBartext{
color: black;
}
.navigator-hover{
background-color: rgba(0, 0, 0, 0);
}
⽽后在全局引⼊样式
@import "/pages/travel_shop/travel/tabbar_template/tabbar_template.wxss";
并在每⼀个页⾯的⼦⽂件(wxml、JS)中引⼊相应的内容
wxml 引⼊
<import src="/pages/travel_shop/travel/tabbar_template/tabbar_template.wxml"/>
<template is="tabBar" data="{{tabBar:bindData.tabBar}}"/>
JS 引⼊
var template = require("../tabbar_template/tabbar_template.js");
并在对应的 onLoad ⽣命周期中,注明它是哪⼀个 tabbar
onLoad: function (options) {
template.tabbar("tabBar", 1, this) //0表⽰第⼀个tabbar,这⾥1表⽰第⼆个 tabbar 的 icon
},
效果预览
我们最终得到了效果,但这种效果带了明显的抖动闪烁。原因则是因为这种实现⽅式的本质是通过 navigator 和 JS 事件触发实现页⾯之间的跳转。因此我开始寻另⼀种实现的⽅式。在这篇博客的留⾔板,我发现该⽂的作者也发现了这种⽅式的不⾜,并提到可以通过可以把页⾯都写成组件 component 的⽅式实现更好的效果。
template 模板 / component 组件
在继续查阅了⼀些关于⼩程序⾃定义 tabbar 的博客之后,到了这篇博⽂。按照这篇博⽂描述的结构,我也进⾏了尝试。发现这种⽅式不会出现之前跳转产⽣的那种闪烁现象出现。
之后再查阅这篇博⽂的时候了解到,如果当我们主要是为了展⽰页⾯的时候,可以使⽤ template ⽅式。如果涉及到 tabbar 对应各个页⾯的业务逻辑交互⽐较多,那就最好使
⽤ component 组件。
因为这三个页⾯涉及到了很多独⽴的交互,所以我决定使⽤ component 组件的形式,将⾃定义的 tabbar 写成⼀个页⾯,然后将其他三个 tabbar 按钮对应的页⾯写成三个 component 组件。这种⽅法和 Vue 中的组件化很相似,可以把单个组件⽂件夹当成 Vue 中的⼀个 .vue ⽂件。
component 与普通 page 类似,但是 JS ⽂件和 JSON ⽂件与页⾯不同。
⼩程序组件 JS 模板
Component({
/* 开启全局样式使⽤ */
options: {
addGlobalClass: true,
},
/* 组件的属性列表 */
properties: {
name: {
type: String,
}
},
/* 组件的初始数据 */
data: {
},
/* ⽣命周期函数 */
lifetimes: {
attached: function () { },
moved: function () { },
detached: function () { },
},
/* 组件的⽅法列表 */
methods: {
},
})
component 组件 JSON ⽂件
{
"component": true,
"usingComponents": {}
}
tabbar 引⽤和配置
引⽤组件 JSON
按照如图的结构,三个 component 作为⼦组件,tabber 作为⼀个⽗级,因此它的 JSON 需要引⼊这三个 component 组件。// travel.json
{
"usingComponents": {
"travel_car": "travel_car/travel_car",
"travel_index": "travel_index/travel_index",
"travel_my": "travel_my/travel_my"
}
}
tabbar JS
⽽该页⾯的 JS 仅仅只⽤来控制 tabbar 的 icon 选择,和传递⼀个 index 告诉页⾯该隐藏和显⽰哪⼀个 component 组件。
// travel.js
let app = getApp()
Page({
data: {
currentTab: 0,
items: [
{
"iconPath": "/pages/img/tab_icon_home@2x.png",
"selectedIconPath": "/pages/img/tab_icon_home_sel@2x.png",
"text": "⾸页"
},
{
"iconPath": "/pages/img/tab_icon_shop@2x.png",
"selectedIconPath": "/pages/img/tab_icon_shop_sel@2x.png",
"text": "购物车"
},
{
"iconPath": "/pages/img/tab_icon_my@2x.png",
"selectedIconPath": "/pages/img/tab_icon_my_sel@2x.png",
"text": "我的"
}
]
},
//事件处理函数
bindChange: function (e) {
let that = this;
that.setData({
currentTab: e.detail.current
});
},
swichNav: function (e) {
let that = this;
if (this.data.currentTab === e.target.dataset.current) {
} else {
that.setData({
currentTab: e.target.dataset.current
})
}
},
onLoad: function () {
let that = this
that.setData({
userInfo: userInfo
})
})
}
})
tabbar WXML
直接使⽤之前 JSON 中引⽤过的标签名,类似于 Vue 中使⽤模板标签。这⾥由于组件模板标签不⽀持直接使⽤ hidden 属性,所以在外包裹了⼀层 view 标签⽤来添加 hidden属性。
<view hidden="{{currentTab == 0? false: true}}">
<travel_index/>
</view>
<view hidden="{{currentTab == 1? false: true}}">
<travel_car/>
</view>
<view hidden="{{currentTab == 2? false: true}}">
<travel_my/>
</view>
<view class="nav-tabs">
<view class="tab-list {{currentTab == idx ? 'active' : 'default' }}" wx:for="{{items}}" wx:key="prototype" wx:for-index="idx" wx:for-item="item" data-current="{{idx}}" bindtap="swichNav"> <text class="tab-text" wx:for-index="idx" data-current="{{idx}}" src="{{currentTab == idx ? item.selectedIconPath : item.iconPath }}">{{}}</text>
<image class="iconPath" wx:for-index="idx" data-current="{{idx}}" src="{{currentTab == idx ? item.selectedIconPath : item.iconPath }}"></image>
</view>
</view>
tabbar WXSS
Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors.(./pages/xxx/xxx.wxss:288:3)This wxss file is ignored.
造成这种报错的原因是 component 组件的样式中不能包含⼀些特定的选择器。
page {
display: flex;
flex-direction: column;
height: 100%;
}
.nav-tabs {
width: 100%;
display: flex;
position: fixed;
bottom: 0;
}
.tab-list {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column-reverse;
background: #fcfcfc;
}
.tab-text {
font-size: 24rpx;
line-height: 35rpx;
color: #5f5f5f;
}
.iconPath {
width:54rpx;
height: 54rpx;
}
.tab-content {
flex: 1;
}
.
default {
line-height: 75rpx;
text-align: center;
flex: 1;
color: #eee;
font-size: 28rpx;
}
.active {
line-height: 75rpx;
text-align: center;
color: black;
flex: 1;
font-weight: bold;
font-size: 28rpx;
}
.show {
display: block;
flex: 1;
}
.hidden {
display: none;
flex: 1;
}
预览效果
最终就完成了⼀个⾮原⽣⼩程序 tabbar 的⾃定义 tabbar 。
Github
在这篇⽂章发布之后,有⼀些朋友询问。我重新整理了⼀个⽐较清晰整洁的 Demo 发布在了 GitHub。如果这个 Demo 能够帮助到您。请不要吝惜您的 Star 。
Demo地址:
参考