背景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>