时隔这么久,终于又开始写博客了:P。本文记录的是本人在做微信小程序的时候,自己实现的几种tab栏。排序从简单到复杂。我实现了以下几种:
效果图如下:
做这个效果并没有什么难点。通过js变量控制点击态,来确定是否需要active样式。下方的bar使用border-bottom实现就可以了。整个nav使用flex布局,调整justify-content为space-around。
注意点:
源代码:
wxml:
<view class="page">
<view class="navBar">
<block wx:for="{
{tabs}}" wx:key="item">
<view id="{
{index}}" class="navItem {
{tabIndex == index ? 'active' : '' }}" bindtap="onTabClick">
<view class="navItemTitle">{
{item}}view>
view>
block>
view>
<view class="content_wrapper">
<block wx:if="{
{tabIndex == 0}}">
<view>新闻页面view>
block>
<block wx:if="{
{tabIndex == 1}}">
<view>活动页面view>
block>
<block wx:if="{
{tabIndex == 2}}">
<view>附近页面view>
block>
view>
view>
wxss:
.navBar {
display: flex;
justify-content: space-around;
height: 100rpx;
position: relative;
background: #fff;
}
.navItem {
display: flex;
align-items: center;
justify-content: center;
border-bottom: 4rpx solid transparent;
}
.active {
color: blue;
border-bottom: 4rpx solid blue;
}
.content_wrapper{
flex: 1;
}
js:
Page({
/**
* 页面的初始数据
* tabs:tab栏的栏目名
* tabIndex:当前tab所在的index
*/
data: {
tabs: ['新闻', '活动', '附近'],
tabIndex: 0
},
// 处理点击tab
onTabClick(e) {
let id = e.currentTarget.id;
this.setData({
tabIndex: id,
})
},
})
效果图:
这个效果的tab想必已经可以满足大部分的需求了。底部内容区域使用swiper组件来完成滑动的效果,并且通过tabIndex来完成上下联动(在swiper中作为current存在)。
注意点:
即将宽度设置为一个tab整体的宽度了。
源码中我仍然使用第一种,第二种的实现方式注释在下方。根据自己需要来选择。
我个人比较推荐使用第二种(长条)。具体原因如下:
源代码:
wxml:
<view class="page">
<view class="navBar">
<block wx:for="{
{tabs}}" wx:key="item">
<view id="{
{index}}" class="navItem {
{tabIndex == index ? 'active' : '' }}" bindtap="onTabClick">
<view class="navItemTitle">{
{item}}view>
view>
block>
<view class="navbar-slider" style="left: {
{
sliderLeft}}px; width:32px; transform: translateX({
{
sliderOffset}}px); -webkit-transform: translateX({
{
sliderOffset}}px); transition:all linear .5s;">view>
view>
<swiper class="content_wrapper" duration='300' bindtransition="swiperTran" bindanimationfinish="animationfinish" current="{
{tabIndex}}" bindchange='swiperChange' data-index='{
{tabIndex}}'>
<swiper-item class="content_page" style="background:#bfa;">{
{tabIndex}}新闻页面swiper-item>
<swiper-item class="content_page" style="background:skyblue;">{
{tabIndex}}活动页面swiper-item>
<swiper-item class="content_page" style="background:orange;">{
{tabIndex}}附近页面swiper-item>
<swiper-item class="content_page" style="background:red;">{
{tabIndex}}视频页面swiper-item>
<swiper-item class="content_page" style="background:blue;">{
{tabIndex}}休闲页面swiper-item>
<swiper-item class="content_page" style="background:#fff;">{
{tabIndex}}文章页面swiper-item>
<swiper-item class="content_page" style="background:pink;">{
{tabIndex}}体育页面swiper-item>
swiper>
view>
wxss:
/* pages/swiper/swipr.wxss */
.navBar {
display: flex;
justify-content: space-around;
height: 100rpx;
position: relative;
background: #fff;
}
.navItem {
display: flex;
align-items: center;
justify-content: center;
}
@keyframes barAnimate{
from{
background: blue
}
to{
background: rgba(0, 0, 255, .1)
}
}
.navbar-slider{
position: absolute;
bottom: 0;
height: 4rpx;
background: blue;
animation: barAnimate linear 1s infinite alternate-reverse;
}
.active {
color: blue;
}
.content_wrapper{
flex: 1;
}
.content_page{
font-size: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
js:
// pages/swiper/swiper.js
Page({
/**
* 页面的初始数据
* tabs:tab栏的栏目名
* tabIndex:当前点击的tab的索引
* itemWidth:每个tab项占的宽。在onload中计算得来
* sliderLeft:初始时slidebar的偏移,为了和项目对齐(受限于字数,且需要调整条的宽度)
* sliderOffset:点击/滑动tab时,应该产生的slider偏移,用于transform。取自于offsets数组
* sliderOffsets:一个数组,记录了每个tab项对应的偏移量。在onload时初始化
*/
data: {
tabs: ['新闻', '活动', '附近', '视频', '休闲', '文章', '体育'],
itemWidth: 0,
windowWidth: 0,
tabIndex: 0,
sliderLeft: 0,
sliderOffset: 0,
sliderOffsets: [],
},
onLoad() {
let that = this;
// 计算
wx.getSystemInfo({
success: function(res) {
// 每个item应占的宽度向上取整,限tab栏不会滑动的情况。
let windowWidth = res.windowWidth;
let itemWidth = Math.ceil(windowWidth / that.data.tabs.length);
// 初始化每个项目的偏移量,存入数组
let tempArr = [];
for (let i in that.data.tabs) {
tempArr.push(itemWidth * i);
}
// 32是两个字体(16px)的宽度。tab中字数不同的话需要调整...
that.setData({
sliderLeft: (res.windowWidth / that.data.tabs.length - 32) / 2,
sliderOffsets: tempArr,
sliderOffset: 0,
itemWidth: itemWidth,
windowWidth: windowWidth
});
}
});
},
// 处理点击tab
onTabClick(e) {
let id = e.currentTarget.id;
// 取新的偏移,完成transition和transform的参数
let newOffset = this.data.sliderOffsets[id];
this.setData({
tabIndex: id,
sliderOffset: newOffset
})
},
// 处理swiper改变
swiperChange(e) {
this.setData({
sliderOffset: this.data.sliderOffsets[e.detail.current],
tabIndex: e.detail.current,
})
},
// 处理手动滑动swiper-item.可以进一步对滚动条进行动画优化,但并不推荐。需要的话将注释放开即可
swiperTran(e){
console.log("滑动tab中...")
},
// 滑动动画完毕后执行的方法(不管滑动是否完成切换)
animationfinish(e){
console.log("动画执行完毕触发animationfinish")
}
})
效果图:
在第二种Tab中,只有我们完成了滑动或点击,bar才会进行切换动画。其本质原因是偏移left并没有随时的进行改变。
观察上一个tab的wxml和js文件,我预留了两个函数:swiperTran和animationfinish。它们对应了swiper中的两个属性:bindtransition(滑动过程中将一直触发)和bindanimationfinish(滑动动画结束后将触发一次)。
想要做到随滑动变化位置,只需要对这两个方法动手就可以了。
注意点:
// pages/swiper/swiper.js
Page({
/**
* 页面的初始数据
* tabs:tab栏的栏目名
* tabIndex:当前点击的tab的索引
* itemWidth:每个tab项占的宽。在onload中计算得来
* sliderLeft:初始时slidebar的偏移,为了和项目对齐(受限于字数,且需要调整条的宽度)
* sliderOffset:点击/滑动tab时,应该产生的slider偏移,用于transform。取自于offsets数组
* sliderOffsets:一个数组,记录了每个tab项对应的偏移量。在onload时初始化
*/
data: {
tabs: ['新闻', '活动', '附近', '视频', '休闲', '文章', '体育'],
itemWidth: 0,
windowWidth: 0,
tabIndex: 0,
sliderLeft: 0,
sliderOffset: 0,
sliderOffsets: [],
},
onLoad() {
let that = this;
// 计算
wx.getSystemInfo({
success: function(res) {
// 每个item应占的宽度向上取整,限tab栏不会滑动的情况。
let windowWidth = res.windowWidth;
let itemWidth = Math.ceil(windowWidth / that.data.tabs.length);
// 初始化每个项目的偏移量,存入数组
let tempArr = [];
for (let i in that.data.tabs) {
tempArr.push(itemWidth * i);
}
// 32是两个字体(16px)的宽度。tab中字数不同的话需要调整...
that.setData({
sliderLeft: (res.windowWidth / that.data.tabs.length - 32) / 2,
sliderOffsets: tempArr,
sliderOffset: 0,
itemWidth: itemWidth,
windowWidth: windowWidth
});
}
});
},
// 处理点击tab
onTabClick(e) {
let id = e.currentTarget.id;
// 取新的偏移,完成transition和transform的参数
let newOffset = this.data.sliderOffsets[id];
this.setData({
tabIndex: id,
sliderOffset: newOffset
})
},
// 处理swiper改变
swiperChange(e) {
this.setData({
sliderOffset: this.data.sliderOffsets[e.detail.current],
tabIndex: e.detail.current,
})
},
// 处理手动滑动swiper-item.可以进一步对滚动条进行动画优化,但并不推荐。需要的话将注释放开即可
swiperTran(e){
console.log("滑动tab中...")
let dx = e.detail.dx;
//console.log(dx);
let index = e.currentTarget.dataset.index;
let windowWidth = this.data.windowWidth;
let itemWidth = this.data.itemWidth;
if (dx > 0) {
// 右滑,dx的值代表了swiper item的位移,正为右滑 负为左滑
if (index < this.data.tabs.length - 1) {
//最后一页不可向右滑动
let ratio = dx / windowWidth; /*滑动比例,计算此时的位移占到了屏幕宽度的多少*/
// 在当前offset的基础上,计算新的偏移
let newOffset = ratio * itemWidth + this.data.sliderOffsets[index];
// console.log(newOffset,",index:",index);
this.setData({
sliderOffset: newOffset,
})
}
} else {
//左滑
if (index > 0) {
//
let ratio = dx / windowWidth; /*滑动比例*/
let newOffset = ratio * itemWidth + this.data.sliderOffsets[index];
// console.log(newOffset, ",index:", index);
this.setData({
sliderOffset: newOffset,
})
}
}
},
// 滑动动画完毕后执行的方法(不管滑动是否完成切换),完成滑动后归位
animationfinish(e){
console.log("动画执行完毕触发animationfinish")
this.setData({
sliderOffset: this.data.sliderOffsets[e.detail.current],
tab1Index: e.detail.current,
})
}
})