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