基於vue與vux作的可滑動tab組件(附源碼)

 

背景javascript

  前不久,剛完成了一個商品列表+購物車功能的頁面,由於一級商品分類在頂部tab中顯示,可滑動,間距可定製,以下圖所示:css

 

定製的tab需求以下:html

  1. 每一個tab-item的間距是相同的,可定製java

  2. 每個tab-item的寬度是隨着文字的增多而寬度增大web

  3. 當tab-item小於等於4個時,tab-item填滿當前屏幕,平分剩餘空間;當tab-item超過4個時,tab可滑動選擇dom

  4. 點擊tab-item時,底部橫線居中顯示,跟隨在點擊的tab-item底部ide

  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 

 

 

完整代碼

 

只要配置了vux環境,就能直接運行此組件)

 

<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用來設置組件去除橫向滾動條顯示-->
<style scoped>
  /*定義滾動條高寬及背景 高寬分別對應橫豎滾動條的尺寸,在這裏設置滾動條寬度爲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用來設置vux組件的部分樣式調整-->
<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>
相關文章
相關標籤/搜索