- 前端
- UI组件
- 移动开发
【免费下载链接】cube-ui
:large_orange_diamond: A fantastic mobile ui lib implement by Vue
导读
本文围绕 cube-ui 移动端组件库中的cube-tab-bar(选项卡栏)展开,完整覆盖其两种渲染形态(data 数据驱动与插槽自定义)、与cube-tab-panels的联动切换机制、showSlider下划线跟随动画的原理实现,以及setSliderTransform实例方法与cube-slide、cube-scroll组合搭建原生 App 风格布局的实战方案。读完本文,你将能够独立在 Vue 项目中配置、定制并组合使用这套 Tab 体系,同时理解其底层 DOM 计算与事件派发逻辑。
该组件自 cube-ui1.10.0 版本新增,本文描述的行为以仓库当前源码(含 1.12.5 版本引入的
value属性)为准。
组件概览与引入方式
cube-tab-bar是一个选项卡容器组件,它支持两种使用方式:
- 默认插槽渲染:直接传入
data数组,由组件内部自动渲染出多个cube-tab子项; - 自定义插槽渲染:手动编写
cube-tab作为第一层级的子组件,包裹自定义内容(如 icon 图标 + 文案),实现类似 App 底部选项卡的效果。
从仓库源码看,TabBar 与 TabPanels 分别封装为独立模块,通过 Vue 插件机制注册:modules/tab-bar/index.js 在install时同时注册cube-tab-bar与cube-tab,并将Tab挂载为TabBar.Tab静态属性;modules/tab-panels/index.js 同理注册cube-tab-panels与cube-tab-panel,挂载Panel静态属性。因此在 src/index.js 中会同时导出TabBar与TabPanels两个插件:
import { TabBar, TabPanels } from 'cube-ui' // 或按需引入 import TabBar from 'cube-ui/lib/tab-bar' import TabPanels from 'cube-ui/lib/tab-panels' Vue.use(TabBar) Vue.use(TabPanels)单元测试 test/unit/specs/tab-bar.spec.js 也验证了Vue.use()之后Vue.component均能正确注册组件。
默认样式:data 数据驱动渲染
传入如下tabs数据结构即可初始化cube-tab-bar:
<template> <cube-tab-bar v-model="selectedLabelDefault" :data="tabs" @click="clickHandler" @change="changeHandler"> </cube-tab-bar> </template>export default { data () { return { selectedLabelDefault: 'Vip', tabs: [{ label: 'Home', icon: 'cubeic-home' }, { label: 'Like', icon: 'cubeic-like' }, { label: 'Vip', icon: 'cubeic-vip' }, { label: 'Me', icon: 'cubeic-person' }] } }, methods: { clickHandler (label) { // if you clicked home tab, then print 'Home' console.log(label) }, changeHandler (label) { // if you clicked different tab, this methods can be emitted } } }关键使用约定:
- 必须使用
v-model指令来选中对应的 tab,v-model的参数值必须与某一项 tab 的label属性对应(1.12.5 版本后改为与value属性对应); icon属性用作 class 选择器,一般搭配字体图标样式使用(如cubeic-home);click与change事件在特定时机派发,参数是每次选中的 tab 对应的 label 值(1.12.5 版本后是 value 值)。
从源码看,data驱动渲染发生在 tab-bar.vue 的默认插槽中:组件遍历data数组,将每一项的label、value、icon透传给内部cube-tab,并以item.value || item.label作为key。1.12.5+ 版本中,data数组的每一项支持label、icon、value三个字段,其中value默认等于label。
而click与change的区分逻辑在 trigger 方法 中一目了然:每次点击cube-tab都会派发click,但只有当新值与当前value不同时,才会继续派发input(驱动 v-model 更新)和change。这也解释了为什么"点击当前已选中的 tab 不会触发 change"。
自定义插槽:icon + 文案的 App 式选项卡
实际业务中更常见的需求是"图标 + 文字"的搭配效果,此时应使用插槽方式。注意必须搭配cube-tab组件作为第一层级的子组件,来包裹你的自定义插槽:
<template> <cube-tab-bar v-model="selectedLabelSlots" show-slider inline @click="clickHandler"> <cube-tab v-for="(item, index) in tabs" :label="item.label" :key="item.label"> <!-- name为icon的插槽 --> <i slot="icon" :class="item.icon"></i> <!-- 默认插槽 --> {{item.label}} </cube-tab> </cube-tab-bar> </template>export default { data () { return { selectedLabelSlots: 'Like', tabs: [{ label: 'Home', icon: 'cubeic-home' }, { label: 'Like', icon: 'cubeic-like' }, { label: 'Vip', icon: 'cubeic-vip' }, { label: 'Me', icon: 'cubeic-person' }] } }, methods: { clickHandler (label) { // if you clicked home tab, then print 'Home' console.log(label) } } }cube-tab的插槽定义(见 tab.vue)为:
| 插槽名称 | 说明 | | - | - | | default |cube-tab渲染的文案 | | icon | 一般是用来添加 icon 图标 |
当cube-tab未传入任何插槽内容时,icon 插槽会退化为渲染iconprop 对应的<i>元素,默认插槽则通过v-html输出label文案。示例页面 tab-bar.vue 中还演示了一种"仅显示图标"的写法——用空的<span></span>覆盖默认插槽。
同时支持三个外观配置项:
showSlider:控制是否开启下划线跟随效果(Boolean,默认false);inline:决定 icon 与 label 是否显示在同一行(Boolean,默认false);useTransition:控制下划线是否使用 transition 过渡(Boolean,默认true)。
这三个配置项在 tab-bar.vue 的 props 声明中均有对应实现:inline为真时组件根元素添加cube-tab-bar_inlineclass,使每个cube-tab内部变为 flex 布局让 icon 与文字同行排列;showSlider为真时渲染出绝对定位于底部的.cube-tab-bar-slider滑块元素。
下划线跟随的实现原理
开启showSlider后,滑块(高 2px 的横条)会跟随当前选中 tab 移动。其底层逻辑位于 tab-bar.vue:
- 注册子组件:每个
cube-tab在mounted时调用this.$parent.addTab(this)把自己登记进tabs数组(tab.vue),销毁时调用removeTab移除; - 计算滑块宽度与位置:
_updateSliderStyle通过findIndex找到当前value对应的 tab,取其$el.clientWidth作为滑块宽度、offsetLeft作为水平位移; - 应用 transform:
setSliderTransform将位移写入滑块的translateX,并设置transform 0.2s linear过渡(仅在useTransition为 true 时)。
setSliderTransform (offset) { const slider = this.$refs.slider if (typeof offset === 'number') { offset = `${offset}px` } if (slider) { if (this.useTransition) slider.style[TRANSITION] = `${TRANSFORM} 0.2s linear` slider.style[TRANSFORM] = `translateX(${offset}) translateZ(0)` } }组件还监听了window.resize事件(防抖 60ms 后重算),确保旋转屏幕或容器尺寸变化时滑块位置依然正确。
联动:CubeTabBar & CubeTabPanels
实际业务中往往需要"tab 切换时显示不同的容器内容",此时需搭配cube-tab-panels组件使用:
cube-tab-panels必须嵌套cube-tab-panel;- 传入
cube-tab与cube-tab-panel的label 值必须一致(1.12.5 版本后改为 value 值一致),以建立"一个 tab 对应一个 panel"的映射关系; - 二者通过相同的
v-model联动。
<template> <cube-tab-bar v-model="selectedLabel" show-slider> <cube-tab v-for="(item, index) in tabs" :icon="item.icon" :label="item.label" :key="item.label"> </cube-tab> </cube-tab-bar> <cube-tab-panels v-model="selectedLabel"> <cube-tab-panel v-for="(item, index) in tabs" :label="item.label" :key="item.label"> <ul> <li class="tab-panel-li" v-for="(hero, index) in item.heroes"> {{hero}} </li> </ul> </cube-tab-panel> </cube-tab-panels> </template>export default { data () { return { selectedLabel: '天辉', tabs: [{ label: '天辉', icon: 'cubeic-like', heroes: ['敌法师', '卓尔游侠', '主宰', '米拉娜', '变体精灵', '幻影长矛手', '复仇之魂', '力丸', '矮人狙击手', '圣堂刺客', '露娜', '赏金猎人', '熊战士'] }, { label: '夜魇', icon: 'cubeic-star', heroes: ['血魔', '影魔', '剃刀', '剧毒术士', '虚空假面', '幻影刺客', '冥界亚龙', '克林克兹', '育母蜘蛛', '编织者', '幽鬼', '司夜刺客', '米波'] }] } } }与 TabBar 类似,cube-tab-panels也支持data数据驱动渲染(此时data每一项为{ label, value }),其面板滑动实现位于 tab-panels.vue:每个cube-tab-panel都是flex: 1 0 auto、宽度 100% 的子容器,父级.cube-tab-panels-group采用 flex 横向排列,通过监听value变化把整个 group 做translateX(-curIndex * 100%)位移,并配合transition: all .4s cubic-bezier(.86, 0, .07, 1)实现平滑的横向滑动切换。每个cube-tab-panel同样在mounted/destroyed时向父组件注册、注销自己。
完整可运行的联动示例见 tab-basic.vue,其中有"天辉 / 夜魇"两套英雄列表的数据驱动切换演示。
实战组合:TabBar + Slide + Scroll 的 App 布局
cube-tab-bar还能搭配其他 cube-ui 组件(如cube-slide、cube-scroll)实现类似原生 App 的顶部 Tab + 内容区横向滑动布局,仓库提供了两个参考示例:
- ScrollTab Demo(scroll-tab.vue):左侧固定宽度的
cube-tab-bar竖排作为分类导航(通过样式覆盖flex-wrap: wrap与width: 100%实现),右侧cube-scroll展示对应英雄列表,change事件里更新数据后调用scrollTo(0, 0)与refresh()重置滚动位置; - tab-composite Demo(tab-composite.vue):顶部
cube-tab-bar(开启show-slider)配合下方cube-slide横向轮播,每个 slide-item 内部再嵌套cube-scroll,实现"关注 / 推荐 / 热榜"三栏内容区。
其中 tab-composite 展示了setSliderTransform实例方法的关键用法:在cube-slide的scroll事件中,把横向滚动距离按比例换算成滑块位移,让下划线跟随滑动手势实时移动:
scroll (pos) { const x = Math.abs(pos.x) const tabItemWidth = this.$refs.tabNav.$el.clientWidth const slideScrollerWidth = this.$refs.slide.slide.scrollerWidth const deltaX = x / slideScrollerWidth * tabItemWidth this.$refs.tabNav.setSliderTransform(deltaX) }同时在cube-slide的change事件中同步selectedLabel,保证 tab 高亮与当前展示页面始终一致。注意:setSliderTransform仅当实例的showSlider属性为true时有效,因为此时才会渲染出滑块 DOM($refs.slider)。
Props 配置总览
CubeTabBar
| 参数 | 说明 | 类型 | 示例 | 默认值 | | - | - | - | - | - | | value | 使用 v-model,初始化时选中对应的 tab | String/Number | - | - | | data | 用于cube-tab-bar渲染的数据,当需要使用内置的默认插槽时此参数必传;数组的每一项是一个 Object,包括label、icon和value(默认值等于label)1.12.5+;如果使用自定义插槽可不传 | Array |[{label: 1, value: 1, icon: 'cubeic-like'}, {label: 2, value: 2, icon: 'cubeic-like'}]|[]| | showSlider | 是否开启下划线跟随效果 | Boolean | true/false | false | | inline | 文字与图标是否显示在一行 | Boolean | true/false | false | | useTransition | 是否开启 transition 过渡 | Boolean | true/false | true |
CubeTab
| 参数 | 说明 | 类型 | 是否必传 | 默认值 | | - | - | - | - | - | | label | 1.12.5 版本前作为哪个 tab 的值作为选中值,1.12.5 版本后主要用作展示 | String/Number | 是 | - | | value | 用于判断哪个 tab 的值作为选中值1.12.5+| String/Number | 否 |label的值 | | icon | 图标 class 名,默认渲染为<i>元素 | String | 否 |''|
CubeTabPanels
| 参数 | 说明 | 类型 | 示例 | 默认值 | | - | - | - | - | - | | value | 使用 v-model,初始化时显示对应的 panels | String/Number | - | - | | data | 用于cube-tab-panels渲染的数据,当需要使用内置的默认插槽时此参数必传;数组的每一项是一个 Object,包括label和value1.12.5+;如果使用自定义插槽可不传 | Array |[{label: 1, value: 1}, {label: 2, value: 2}]|[]|
CubeTabPanel
| 参数 | 说明 | 类型 | 是否必传 | 默认值 | | - | - | - | - | - | | label | 用于显示 panel | String/Number | 是 | - | | value | panel 的 key 值,决定了选中的值1.12.5+| String/Number | 是 |value的值 |
事件
CubeTabBar
| 事件名 | 说明 | 参数 | | - | - | - | | click | 当 tab 被点击时派发 | 点中的 tab 的 label/value1.12.5+值 | | change | 当点击不同的 tab 时派发 | 点中的 tab 的 label/value1.12.5+值 |
关于二者的区别,tab-bar.vue 的 trigger 方法 给出精确语义:click每次点击必发,change仅在选中值真正发生改变时触发(此时同时派发input完成 v-model 回写)。单元测试 tab-bar.spec.js 使用 sinon spy 验证了点击不同 tab 时clickHandler与changeHandler均恰好被调用一次,点击相同 tab 时只触发click。
实例方法
CubeTabBar
当该实例的showSlider属性设置为true时,以下方法才有效。
| 方法名 | 说明 | 参数类型 | | - | - | - | | setSliderTransform | 改变cube-tab-bar组件的下划线的 transformX;如果传 Number,会转成像素,也可以传带有单位的 String | Number/String |
源码实现(tab-bar.vue)确认:传 Number 时自动拼接px单位,传'20px'这类字符串则直接使用;最终滑块样式为translateX(${offset}) translateZ(0),开启useTransition时附带transform 0.2s linear过渡动画。
样式定制
TabBar 的默认配色通过 stylus 主题变量定义,位于 theme/default.styl:
// tab-bar & tab-panel $tab-color := $color-grey $tab-active-color := $color-dark-orange $tab-slider-bgc := $color-dark-orange三个变量分别控制未选中文字颜色、选中 tab 文字颜色与下划线背景色,可通过覆盖 cube-ui 的主题变量实现全局或局部换肤。若只需局部调整,也可直接覆写 CSS,例如 tab-composite.vue 中通过.cube-tab-bar-slider { background-color: black }把下划线改成黑色。
小结
cube-tab-bar提供了"数据驱动 + 插槽自定义"双通道的选项卡渲染能力,配合cube-tab-panels实现内容面板联动、配合cube-slide/cube-scroll搭建复杂 App 布局,showSlider下划线跟随与setSliderTransform方法则让"手势滑动同步滑块"成为可能。使用时只需记住三条核心约定:v-model 绑定选中值、1.12.5 后优先使用value作为关联键、tab 与 panel 的 key 必须一一对应。相关完整示例代码可在仓库的 example/pages/tab-bar 目录下查看。
- 前端
- UI组件
- 移动开发
【免费下载链接】cube-ui
:large_orange_diamond: A fantastic mobile ui lib implement by Vue
相关推荐
如何用3分钟搭建本地唇语识别系统:Chaplin完整指南
如何用3分钟搭建本地唇语识别系统:Chaplin完整指南 想要在完全安静的环境中与电脑进行无声对话吗?Chaplin是一款完全本地运行的实时唇语识别工具,它能通
前端UI组件移动开发throttler模块常见问题解答:从集成错误到性能优化的完整指南
throttler模块常见问题解答:从集成错误到性能优化的完整指南 throttler是NestJS生态中一款强大的限流模块,支持Fastify、Express
前端UI组件移动开发cube-ui Toolbar 工具栏组件实战指南:组合按钮与复选框,实现可展开双层操作栏
cube ui Toolbar 工具栏组件实战指南:组合按钮与复选框,实现可展开双层操作栏 导读 Toolbar(工具栏)是 cube ui 自 1.9.0 版
前端UI组件移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考