微信小程序:各种Tab栏

时隔这么久,终于又开始写博客了:P。本文记录的是本人在做微信小程序的时候,自己实现的几种tab栏。排序从简单到复杂。我实现了以下几种:

  • 最普通的Tab。点击变色并切换,无过渡。
  • 带滑动效果的Tab,配合swiper实现的。
  • 滑动条跟随滑动进度的Tab,是第二种的进阶版。但不推荐使用。

一、最普通的Tab

效果图如下:
微信小程序:各种Tab栏_第1张图片
做这个效果并没有什么难点。通过js变量控制点击态,来确定是否需要active样式。下方的bar使用border-bottom实现就可以了。整个nav使用flex布局,调整justify-content为space-around。
注意点:

  • 如果tab栏非常多,可以使用scroll-view让它可以滑动。但是一般来说微信小程序一个页面最好不要放太多内容,所以这种需求应该不会很常见,一般2-4个tab完全不需要使用scroll-view。
  • 在写active类样式时我们会写被激活的tab项底部的bar,这个bar同样是有高度的,所以我们也需要给每个tab项设置border-bottom,只不过将颜色设置为transparent(透明)。这样的话就可以保证每个项点击前后的高度是相同的。

源代码:
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

效果图:
微信小程序:各种Tab栏_第2张图片
这个效果的tab想必已经可以满足大部分的需求了。底部内容区域使用swiper组件来完成滑动的效果,并且通过tabIndex来完成上下联动(在swiper中作为current存在)。
注意点:

  • 在这种实现方式中,bar由于要使用动画效果,所以不能简单的使用border-bottom去实现,而是使用一个新的view相对于tab栏整体进行绝对定位,并设置bottom为0,调整left偏移来实现的。所以理论上来说如果tab栏过多(超过8个,本例中放了7个)你需要使用scroll-view的话,动画效果会出问题。因为每项的长度是通过屏幕宽度除以tab项数去计算的。
  • bar的样式实现有两种,第一种就是如上图所示,第二种是这样的:

微信小程序:各种Tab栏_第3张图片

即将宽度设置为一个tab整体的宽度了。
源码中我仍然使用第一种,第二种的实现方式注释在下方。根据自己需要来选择。
我个人比较推荐使用第二种(长条)。具体原因如下:

  1. 如果使用短的样式需要给它一个初始的left来和项目对齐,而这个left是需要去计算的。我实现的方式是windowWidth/tab项数得到每个项目的宽度,再减去32(字体为16px,每个tab项里我使用的是2个字)得到左右空白的宽度,再除以2得到初始偏移。同时bar的长度也要和字体一样长,需要将宽度写死为32.
    那么也就是说这个初始偏移的计算和长度是受限于每个tab项的字数的,如果某个tab项它的字数不是2个,而是三个,这个计算就会出错。显示时就不会在正确的位置了。这也是我每个tab项保持字数统一为2个的原因。具体的实现请看js文件的onload函数。而长条的就不需要担心初始偏移的问题,但还是需要注意字数统一。
  2. 在某些尺寸的屏幕上短条观感上可能有些非常轻微的对不齐,不是极度强迫症患者的话可以无视。
  • 由于我们这个bar是用新的view实现的,同时颜色是通过background设置的,大家就可以自由发挥了——你可以使用自己喜欢的图片做bar的背景,或者给它加上动画(本例中bar我就加上了一个动画,让它看起来有呼吸灯的效果。仅做抛砖引玉用)
  • 当tab较多时,比如当前页面有7个,尽量令每个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进阶

效果图:
微信小程序:各种Tab栏_第4张图片
在第二种Tab中,只有我们完成了滑动或点击,bar才会进行切换动画。其本质原因是偏移left并没有随时的进行改变。
观察上一个tab的wxml和js文件,我预留了两个函数:swiperTran和animationfinish。它们对应了swiper中的两个属性:bindtransition(滑动过程中将一直触发)和bindanimationfinish(滑动动画结束后将触发一次)。
想要做到随滑动变化位置,只需要对这两个方法动手就可以了。

注意点:

  • 这个效果看看就好。最好不要使用。因为在bindtransition中会执行setData,在微信官方给出的建议中就建议我们不要频繁(毫秒级)的调用setData,否则会导致页面卡顿或其他bug。就比如本例,如果卡在中间位置一直左右晃动,页面就会鬼畜起来。
    源代码:
    wxml和wxss与上一个tab没有区别。
    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中...")

    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,
    })
  }
})

如果这篇文章帮到了你,欢迎支持~
微信小程序:各种Tab栏_第5张图片

你可能感兴趣的:(微信小程序,css,小程序,html,javascript)