码迷,mamicode.com
首页 > 其他好文 > 详细

基于vue与vux做的可滑动tab组件

时间:2017-12-13 23:27:47      阅读:190      评论:0      收藏:0      [点我收藏+]

标签:计算   outer   native   ack   vue   mount   响应式   运用   就会   

 

背景

前不久,刚完成了一个商品列表+购物车功能的页面,因为一级商品分类在顶部tab中显示,可滑动,间距可定制,如下图所示:

技术分享图片

 

定制的tab需求如下:

1. 每个tab-item的间距是相同的,可定制

2. 每一个tab-item的宽度是随着文字的增多而宽度增大

3. 当tab-item小于等于4个时,tab-item填满当前屏幕,评分剩余空间;当tab-item超过4个时,tab可滑动选择

4. 点击tab-item时,底部横线居中显示,跟随在点击的tab-item底部

5. 从上一个页面点击一级分类,进入此页面,显示上一页面点击的一级分类名称,居中显示,样式高亮

 

成品效果如下:

技术分享图片

这个组件基本满足上面5点需求

 

难点

1)使用vux的可滑动的tab,修改组件css,如何令到每一个tab的间距为响应式的。

2)这个组件最核心的就是底部bar的精准定位跟随

3)从前一个页面点击一级分类进入商品列表页,自动选中并在屏幕居中显示被选中的tab-item

 

 

前期知识点

1)offsetLeft:子元素相对于父元素最左上角侧的横向偏离位置

2)offsetWidth: 元素的宽度

3)scrollLeft: 滑动到对应的x坐标

4)定位元素style.left的运用

5)vux组件之滑动tab的运用 (需要用到组件自带的onItemClick()方法,通过dom,可以起到点击该tab-item的作用)

 

难点逐一破解

1)使用vux的可滑动的tab,修改组件css,如何令到每一个tab的间距为响应式的。

原本vux的可滑动的tab是根据scrollWidth的长度来自动计算每一个tab-item的宽度的,因为包含这tab-item的tabBox这个div使用的是flex布局,而tab-item是它的子元素,它会自动沾满tabBox。如果文字超出了tab-item的宽度,文字就会被隐藏。

可以通过修改vux-tab-item这个样式来自定义样式,把子元素的弹性属性去除,并且设置他的padding,这样可以呈现出文字能显示全,并且每个tab-item间距相同的效果,css如下:

/*改变原来tabBox的flex布局*/
  .tab-component .vux-tab .vux-tab-item {
    display: inline-block;
    width: auto;
    height: 100%;
    padding: 0 10px;
    flex: none;
    background-color: #f2f4f5;
}

 

2)这个组件最核心之一的就是底部bar的精准定位跟随

因为上面的1)改变了布局,所以导致底部bar跟随不准确的情况,我们可以定制bar。在vux里面,bar是一个div,它有滑动的动画,我的做法是这样的,首先通过right让它置于tab的最左侧,然后通过按钮点击事件获得相对应的tab-item元素的下标,然后使用for循环从第一tab-item开始寻找,如果不为改元素,则把它的元素宽度进行累加,直到找到该需激活的tab-item,然后通过数学计算可把bar定位在该元素的底部并且居中,代码如下:

onItemClick(keyword, index) {
     console.log(‘on item click:‘, index)
     let barLeft = 0;
     document.getElementsByClassName(‘vux-tab-ink-bar‘)[0].style.right = ‘100%‘;
     for (let i = 0; i < this.list.length;) {
       if (document.getElementsByClassName(‘vux-tab-item‘)[i].innerText === keyword) {
         console.log(‘document.getElementsByClassName(\‘vux-tab-item\‘)[‘ + index + ‘].offsetWidth = ‘ + document.getElementsByClassName(‘vux-tab-item‘)[i].offsetWidth)
         barLeft += document.getElementsByClassName(‘vux-tab-item‘)[i].offsetWidth / 2
         //为什么是22.5?因为底部bar长度为44px,这样做可以让bar的中心对齐tab-item的中心
barLeft -= 22.5 break; } barLeft += document.getElementsByClassName(‘vux-tab-item‘)[i].offsetWidth; i += 1; } document.getElementsByClassName(‘vux-tab-ink-bar‘)[0].style.left = (barLeft + ‘px‘); },

  

3)从前一个页面点击一级分类进入商品列表页,自动选中并在屏幕居中显示被选中的tab-item

思路是这样的:

当tab-item的数量为4个或者以下的时候,获取当前屏幕宽度,然后评分长度,计算之后,平均分给tab-item,因为每一个tab-item自己的样式中有设置的padding属性,所以间距相同,不需要额外为间距分配空间。

当tab-item的数量超过4个,则不需要分配宽度,因为是flex布局的子元素,每一个tab-item会根据自己的文字得到自己的宽度。

最重要最核心的来了,如何让选中的tab-item居中显示,例如,屏幕为320px, 需要居中显示的tab-item(简称SItem)距离屏幕最右侧1000px,SItem本身长度为60,问现在如何让SItem居中在长度为320px的屏幕当中?

技术分享图片

-----------------------------------------------------------------------------------------------

通过下面这段代码

// 伪代码
this.$refs.tabBoxOuter.scrollLeft = SItem.offsetLeft

可以把上面这种状态变为下图:

技术分享图片

-------------------------------------------------------------------------------

通过下面这段代码,就可以把上图的两黑点中心在垂直方向上重合,并且滚动显示在屏幕上面
this.$refs.tabBoxOuter.scrollLeft = SItem.offsetLeft - tabConter 

 

 

完整代码

 

<template>
  <div class="tab-component">
    <divider>tab组件</divider>
    <div ref="tabBoxOuter" style="width: 100%;overflow:scroll;-webkit-overflow-scrolling:touch;">
      <tab ref="tabBox" style="background-color: #f2f4f5;font-size: 14px" bar-active-color="#149c81" :line-width="4"
           :custom-bar-width="getBarWidth" :style="{width: tabWidth + ‘px‘}">
        <tab-item v-for="(item,index) in list" :key="index" @on-item-click="onItemClick(item, index)">{{item}}
        </tab-item>
      </tab>
    </div>
    <br/>
    <div class="box">
      <x-button @click.native="clickTabItemById(2)" type="primary">go to 2</x-button>
      <x-button @click.native="clickTabItemById(3)" type="primary">go to 3</x-button>
      <x-button @click.native="clickTabItemById(4)" type="primary">go to 4</x-button>
      <x-button @click.native="clickTabItemById(5)" type="primary">go to 5</x-button>
      <x-button @click.native="clickTabItemById(6)" type="primary">go to 6</x-button>
      <x-button @click.native="clickTabItemById(7)" type="primary">go to 7</x-button>
    </div>
  </div>
</template>


<script>
  import { Tab, TabItem, Divider, XButton } from ‘vux‘


  export default {
    components: {
      Tab,
      TabItem,
      Divider,
      XButton,
    },
    data() {
      return {
        // tab标签div长度
        tabWidth: document.body.clientWidth,
        list: [‘打印机‘, ‘复印机‘, ‘打印纸‘, ‘订书机11111111‘, ‘打印机2222222222222222‘, ‘复印机3333333333333‘, ‘打印纸444444444444‘, ‘订书机5‘]
      }
    },
    mounted() {
      this.setTabWidth()
      this.clickFirstItem()
    },
    methods: {
      onItemClick(keyword, index) {
        console.log(‘on item click:‘, index)
        let barLeft = 0;
        document.getElementsByClassName(‘vux-tab-ink-bar‘)[0].style.right = ‘100%‘;
        for (let i = 0; i < this.list.length;) {
          if (document.getElementsByClassName(‘vux-tab-item‘)[i].innerText === keyword) {
            console.log(‘document.getElementsByClassName(\‘vux-tab-item\‘)[‘ + index + ‘].offsetWidth = ‘ + document.getElementsByClassName(‘vux-tab-item‘)[i].offsetWidth)
            barLeft += document.getElementsByClassName(‘vux-tab-item‘)[i].offsetWidth / 2
            barLeft -= 22.5
            break;
          }
          barLeft += document.getElementsByClassName(‘vux-tab-item‘)[i].offsetWidth;
          i += 1;
        }
        document.getElementsByClassName(‘vux-tab-ink-bar‘)[0].style.left = (barLeft + ‘px‘);
      },
      // 函数控制tab-bar的宽度,如果tab标签页数量为1,则隐藏tab-bar
      getBarWidth() {
        if (this.list && this.list.length === 1) {
          return ‘0px‘
        }
        return ‘45px‘
      },
      setTabWidth() {
        // 页面完成刷新之后
        this.$nextTick(() => {
          let ofwidth = 0;
          let efwidth = 0;
          // efwidth为每一个tab-item的长度总和,因为tab-item的父级为flex布局,而tab-item的flex: none,所以初始化的时候,tab-item会根据自己的字体长度,自动扩张宽度。
          for (let i = 0; i < this.$refs.tabBox.$children.length;) {
            efwidth += this.$refs.tabBox.$children[i].$el.offsetWidth;
            i += 1;
          }
          // 同样是计算初始化的时候,每一个tab-item的总宽度,但当tab-item总长度大于tab的总长度时,立马退出程序
          for (let i = 0; i < this.$refs.tabBox.$children.length;) {
            ofwidth += this.$refs.tabBox.$children[i].$el.offsetWidth;
            if (ofwidth > (document.body.clientWidth)) {
              break;
            }
            i += 1;
          }
          // 假如tab-item的总宽度小于显示tabwidth,则评分tab的剩余空间,加到每一个tab-item中
          if (ofwidth < (document.body.clientWidth)) {
            for (let i = 0; i < this.$refs.tabBox.$children.length;) {
              this.$refs.tabBox.$children[i].$el.style.width = (this.$refs.tabBox.$children[i].$el.clientWidth + (((document.body.clientWidth) - ofwidth) / this.$refs.tabBox.$children.length)) + ‘px‘;
              console.log(((((document.body.clientWidth) - ofwidth) / this.$refs.tabBox.$children.length)) + ‘px‘)
              i += 1
            }
            this.tabWidth = (document.body.clientWidth);
          } else {
            this.tabWidth = efwidth;
          }
        }, 1000)
      },
      clickFirstItem() {
        setTimeout(() => {
          this.$refs.tabBox.$children[0].onItemClick()
        }, 200)
      },
      clickTabItemById(index) {
        // 模拟点击事件
        this.$refs.tabBox.$children[index].onItemClick();
        // 滑动到对应的点击标签页
        // 这里值得注意的是,为什么tabBoxOut的宽度明明只有屏幕的宽度,而里面的tabBox是超过屏幕的宽度的,所有才
        // 可以滑动,滑动的是tabBox这个div,而真正滑动的事件却是绑定在tabBoxOut这个div当中。所以,当你使用scrollLeft
        // 这个属性的时候,是要用在tabBoxOut这个div上,而不是在tabBox这个div上。
        // ----------------------------------------------------------------
        // 接下来可以运用offsetLeft计算tab-item在父div tabBox横轴偏移量、scrollLeft滑动到对应的tab-item,然后运用数学公式来把激活的tab-item滚动到tabBoxOuter这个div
        // 的中心
        let tabConter = (document.body.clientWidth - this.$refs.tabBox.$children[index].$el.offsetWidth) / 2
        this.$refs.tabBoxOuter.scrollLeft = this.$refs.tabBox.$children[index].$el.offsetLeft - tabConter
      },
    }
  }
</script>

<style lang="less" scoped>
  @import ‘~vux/src/styles/1px.less‘;
  @import ‘~vux/src/styles/center.less‘;

  /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸,在这里设置滚动条宽度为0px*/
  ::-webkit-scrollbar {
    width: 0px;
    display: none;
    background-color: #fff;
  }

  /*定义滚动条轨道 内阴影+圆角*/
  ::-webkit-scrollbar-track {
    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
    border-radius: 10px;
    background-color: #fff;
  }

  /*定义滑块 内阴影+圆角*/
  ::-webkit-scrollbar-thumb {
    border-radius: 10px;
    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
    background-color: #fff;
  }
</style>
<style>
  .tab-component .vux-tab-bar-inner {
    border-radius: 10px !important;
  }

  /*改变原来tabBox的flex布局*/
  .tab-component .vux-tab .vux-tab-item {
    display: inline-block;
    width: auto;
    height: 100%;
    padding: 0 10px;
    flex: none;
    background-color: #f2f4f5;
  }

  /*定义tab-item选中时的样式*/
  .tab-component .vux-tab .vux-tab-item.vux-tab-selected {
    font-size: 16px;
    color: #149c81;
    border-bottom: 3px solid #04BE02;
  }

  .box {
    padding: 15px;
  }
</style>

 

  

 

基于vue与vux做的可滑动tab组件

标签:计算   outer   native   ack   vue   mount   响应式   运用   就会   

原文地址:http://www.cnblogs.com/pengshengguang/p/8034255.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!