背景

  前不久,刚完成了一个商品列表+购物车功能的页面,因为一级商品分类在顶部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 

完整代码

只要配置了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>

  

  

基于vue与vux做的可滑动tab组件(附源码)的更多相关文章

  1. 基于jQuery左右滑动切换特效 附源码

    分享一款基于脚jQuery左右滑动切换特效.这是一款鼠标点击左右箭头按钮图片滚动切换,鼠标移到图片上显示透明边框特效.   效果图如下:   废话不多说,代码奉上!   html代码: <div ...

  2. Android应用系列:手把手教你做一个小米通讯录(附图附源码)

    前言 最近心血来潮,突然想搞点仿制品玩玩,很不幸小米成为我苦逼的第一个试验品.既然雷布斯的MIUI挺受欢迎的(本人就是其的屌丝用户),所以就拿其中的一些小功能做一些小demo来玩玩.小米的通讯录大家估 ...

  3. 【Android初级】如何实现一个比相册更高大上的左右滑动特效(附源码)

    在Android里面,想要实现一个类似相册的左右滑动效果,我们除了可以用Gallery.HorizontalScrollView.ViewPager等控件,还可以用一个叫做 ViewFlipper 的 ...

  4. 用Python做了个图片识别系统(附源码)

    本项目将使用python3去识别图片是否为色情图片,会使用到PIL这个图像处理库,并且编写算法来划分图像的皮肤区域 介绍一下PIL: PIL(Python Image Library)是一种免费的图像 ...

  5. 图片管够!用Python做了个图片识别系统(附源码)

    本项目将使用python3去识别图片是否为色情图片,会使用到PIL这个图像处理库,并且编写算法来划分图像的皮肤区域 介绍一下PIL: PIL(Python Image Library)是一种免费的图像 ...

  6. 基于Vue的Quasar Framework 介绍 这个框架UI组件很全面

    基于Vue的Quasar Framework 介绍 这个框架UI组件很全面 基于Vue的Quasar Framework 中文网http://www.quasarchs.com/ quasarfram ...

  7. 基于Redis缓存的Session共享(附源码)

    基于Redis缓存的Session共享(附源码) 在上一篇文章中我们研究了Redis的安装及一些基本的缓存操作,今天我们就利用Redis缓存实现一个Session共享,基于.NET平台的Seesion ...

  8. vue双向绑定的原理及实现双向绑定MVVM源码分析

    vue双向绑定的原理及实现双向绑定MVVM源码分析 双向数据绑定的原理是:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值 ...

  9. [转]Asp.Net大型项目实践(11)-基于MVC Action粒度的权限管理【续】【源码在这里】(在线demo,全部源码)

    本文转自:http://www.cnblogs.com/legendxian/archive/2010/01/25/1655551.html 接上篇Asp.Net大型项目实践(10)-基于MVC Ac ...

随机推荐

  1. c刷题

    1.转义字符: C中定义了一些字母前加 "\" 来表示常见的那些不能显示的ASCII字符,如\0 空字符,\r 回车, \n换行等,就称为转义字符,因为后面的字符,都不是它本来的A ...

  2. OpenGIS

    OpenGIS(Open Geodata Interoperation Specification,OGIS-开放的地理数据互操作规范)由美国OGC(OpenGIS协会,Open Geospatial ...

  3. UML-Based Modeling of Robustness Testing

    一.基本信息 标题:UML-Based Modeling of Robustness Testing 时间:2014 出版源:IEEE会议论文 领域分类:稳健性测试:UML测试Prole:UML Pr ...

  4. shell指令(一)

    ubuntu桌面窗口下进入shell窗口:Ctrl + Alt + F2~F6:  退出shell窗口:Ctrl + Alt + F7:从UI中进入UI命令窗口,Ctrl + Alt +T shell ...

  5. map put值 使用匿名函数

    List<Map<String, Object>> list = setRoleMenuRlMapper.selectMapByParams(params); // Map m ...

  6. Akka-CQRS(6)- read-side

    前面我们全面介绍了在akka-cluster环境下实现的CQRS写端write-side.简单来说就是把发生事件描述作为对象严格按发生时间顺序写入数据库.这些事件对象一般是按照二进制binary方式如 ...

  7. ActiveMQ 的安装与使用

    消息中间件简介 消息中间件(MOM:Message Orient middleware) 消息中间件有很多的用途和优点: 1. 将数据从一个应用程序传送到另一个应用程序,或者从软件的一个模块传送到另外 ...

  8. SpringBoot 项目打包后运行报 org.apache.ibatis.binding.BindingException

    今天把本地的一个SpringBoot项目打包扔到Linux服务器上,启动执行,接口一访问就报错,但是在本地Eclipse中启动执行不报错,错误如下: org.apache.ibatis.binding ...

  9. Metasploit Framework(1)基本命令、简单使用

    文章的格式也许不是很好看,也没有什么合理的顺序 完全是想到什么写一些什么,但各个方面都涵盖到了 能耐下心看的朋友欢迎一起学习,大牛和杠精们请绕道 基本的控制台命令介绍: banner 查看metasp ...

  10. python之asyncio

    asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持. asnycio是用来编写并发代码的库,python3.5以后使用async/await语法. asyncio 被用作 ...