如何在Vue.js中实现标签页组件详解


Posted in Javascript onJanuary 02, 2019

前言

标签页组件,即实现选项卡切换,常用于平级内容的收纳与展示。

因为每个标签页的内容是由使用组件的父级控制的,即这部分内容为一个 slot。所以一般的设计方案是,在 slot 中定义多个 div,然后在接到切换消息时,再显示或隐藏相关的 div。这里面就把相关的交互逻辑也编写进来了,我们希望在组件中处理这些交互逻辑,slot 只单纯处理业务逻辑。这可以通过再定义一个 pane 组件来实现,pane 组件嵌在 tabs 组件中。

1 基础版

因为 tabs 组件中的标题是在 pane 组件中定义的,所以在初始化或者动态变化标题时,tabs 组件需要从 pane 组件中获取标题。

html:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>标签页组件</title>
 <link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>
<div id="app" v-cloak>
 <tabs v-model="activeIndex">
 <pane label="科技">
 火星疑似发现“外星人墓地”?至今无法解释
 </pane>
 <pane label="体育">
 全美沸腾!湖人队4年1.2亿迎顶级后卫,詹姆斯:有他就能夺冠
 </pane>
 <pane label="娱乐">
 阿米尔汗谈中国武侠 想拍印度版《鹿鼎记》
 </pane>
 </tabs>
</div>
<script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script>
<script src="tabs.js"></script>
<script>
 var app = new Vue({
 el: '#app',
 data: {
 activeIndex: 0
 }
 });
</script>
</body>
</html>

pane 组件:

Vue.component('pane', {
 name: 'pane',
 template: '\
 <div class="pane" v-show="isShow">\
 <slot></slot>\
 </div>\
 ',
 props: {
 //标题
 label: {
 type: String,
 default: ''
 }
 },
 data: function () {
 return {
 //显示或隐藏
 isShow: true
 }
 },
 methods: {
 //通知父组件,更新标题
 init() {
 this.$parent.init();
 }
 },
 watch: {
 //当 label 值发生变化时,更新标题
 label() {
 this.init();
 }
 },
 //挂载时,更新标题
 mounted() {
 this.init();
 }
});

在 pane 组件中,我们做了以下设计:

  • 因为 pane 组件需要控制标签页内容的显示与隐藏,所以我们在 data 中定义了一个 isShow,并用 v-show 指令来控制内容的显示或隐藏。当点击这个 pane 所对应的标签页标题时,它的 isShow 被设置为 true。
  • 我们需要一个标识来识别不同的标签页标题,本示例用的是 pane 组件定义顺序的索引。
  • 在 props 中定义了 label,用于存放标题。因为 label 可以动态变化,所以必须在挂载 pane 以及当 label 值发生变化(通过监听实现)时,通知父组件,重新初始化标题。因为 pane 是独立组件,所以这里使用了 this.$parent 来调用父组件 tabs 的初始化方法。

tabs 组件:

Vue.component('tabs', {
 template: '\
 <div class="tabs">\
 <div class="tabs-bar">\
 <!-- 标签页标题-->\
 <div :class="tabClass(item)"\
 v-for="(item, index) in titleList"\
 @click="change(index)">\
 {{ item.label }}\
 </div>\
 </div>\
 <div class="tabs-content">\
 <!-- pane 组件位置-->\
 <slot></slot>\
 </div>\
 </div>',
 props: {
 value: {
 type: [String, Number]
 }
 },
 data: function () {
 return {
 currentIndex: this.value,
 titleList: []//存放标题
 }
 },
 methods: {
 //设置样式
 tabClass: function (item) {
 return ['tabs-tab', {
 //为当前选中的 tab 添加选中样式
 'tabs-tab-active': (item.name === this.currentIndex)
 }]

 },
 //获取定义的所有 pane 组件
 getTabs() {
 return this.$children.filter(function (item) {
 return item.$options.name === 'pane';
 })
 },
 //更新 pane 是否显示状态
 updateIsShowStatus() {
 var tabs = this.getTabs();
 var that = this;
 //迭代判断并设置某个标签页是显示还是隐藏状态
 tabs.forEach(function (tab, index) {
 return tab.isShow = (index === that.currentIndex);
 })
 },
 //初始化
 init() {
 /**
 * 初始化标题数组
 */
 this.titleList = [];
 var that = this;//设置 this 引用
 this.getTabs().forEach(function (tab, index) {
 that.titleList.push({
  label: tab.label,
  name: index
 });

 //初始化默认选中的 tab 索引
 if (index === 0) {
  if (!that.currentIndex) {
  that.currentIndex = index;
  }
 }
 });

 this.updateIsShowStatus();
 },
 //点击 tab 标题时,更新 value 值为相应的索引值
 change: function (index) {
 var nav = this.titleList[index];
 var name = nav.name;
 this.$emit('input', name);
 }
 },
 watch: {
 //当 value 值发生改变时,更新 currentIndex
 value: function (val) {
 this.currentIndex = val;
 },
 //当 currentIndex 值发生改变时,更新 pane 是否显示状态
 currentIndex: function () {
 this.updateIsShowStatus();
 }
 }
});
  • getTabs() 中通过 this.$children 来获取定义的所有 pane 组件。因为很多地方都会用到getTabs() ,所以这里把它单独定义出来。
  • 注意: methods 中如果存在回调函数,那么需要在外层事先定义一个 var that = this;,在 that 中引用 Vue 实例本身,也可以使用 ES2015 的箭头函数。
  • 在初始化方法中,我们通过迭代 pane 组件,初始化了标题数组,label 取定义的标题,name 取所在的索引。 标题数组用于模板定义中。
  • updateIsShowStatus() 用于更新 tab 是否显示状态。之所以独立出来,是为了在监听 currentIndex 发生变化时,也能调用该方法。
  • 在模板定义中,我们使用 v-for 指令渲染出标题,并绑定了 tabClass 函数,从而实现了动态设置样式。因为需要传参,所以不能使用计算属性。
  • 点击每一个 tab 标题时,会触发 change(),来更新 value 值为相应的索引值。在 watch 中,我们监听了 value 值,当 value 值发生改变时,更新 currentIndex。也监听了 currentIndex 值,当 currentIndex 值发生改变时,更新 pane 是否显示状态。

总结如下:

  • 使用组件嵌套方式,将多个 pane 组件作为 tabs 组件的 slot。
  • tabs 组件与 pane 组件,通过父子链(即 $parent 与 $children)实现通信。

样式:

[v-cloak] {
 display: none;
}

.tabs {
 font-size: 14px;
 color: #657180;
}

.tabs-bar:after {
 content: '';
 display: block;
 width: 100%;
 height: 1px;
 background: #d7dde4;
 margin-top: -1px;
}

.tabs-tab {
 display: inline-block;
 padding: 4px 16px;
 margin-right: 6px;
 background: #fff;
 border: 1px solid #d7dde4;
 cursor: pointer;
 position: relative;
}

.tabs-tab:hover {
 color: #336699;
 font-weight: bolder;
}

.tabs-tab-active {
 color: #336699;
 border-top: 1px solid #336699;
 border-bottom: 1px solid #fff;
}

.tabs-tab-active:before {
 content: '';
 display: block;
 height: 1px;
 background: #3399ff;
 position: absolute;
 top: 0;
 left: 0;
 right: 0;
}

.tabs_content {
 padding: 8px 0;
}

.pane {
 margin-top: 26px;
 font-size: 16px;
 line-height: 24px;
 color: #333;
 text-align: justify;
}

效果:

如何在Vue.js中实现标签页组件详解

2 关闭属性

我们为 pane 组件新增一个 closable 属性,用于控制该标签是否可关闭。

在子窗口组件的 props 中,新增 closable 属性:

props: {
	...
	//是否可关闭
	closable: {
		type: Boolean,
		default: false
	}
}

在标签页组件中的模板中,新增关闭标签:

...
template: '\
<div class="tabs">\
	<div class="tabs-bar">\
		<!-- 标签页标题-->\
		<div :class="tabClass(item)"\
			v-for="(item, index) in titleList"\
			@click="change(index)">\
			{{ item.label }}\
			<span v-if="item.closable" class="close" @click="close(index,item.name)"></span>\
			</div>\
		</div>\
		<div class="tabs-content">\
		 <!-- pane 组件位置-->\
			<slot></slot>\
		</div>\
	 </div>',
...
  • 这里使用 v-if 指令,根据 closable 的值来判断是否构建 “关闭” 标签。
  • 点击事件绑定了 close() 函数,传入标签所在索引以及标签的名称。

在标签页组件中的方法中,新增了 close(),用于执行关闭标签页逻辑:

close: function (index, name) {
 //删除对应的标题元素
	this.titleList.splice(index, 1);

	var tabs = this.getTabs();
	var that = this;
	//迭代判断并设置点击的标签页是隐藏状态
	tabs.forEach(function (tab, index) {
		if (index === name) {
			return tab.isShow = false;
		}
	});
}
  • 首先在标题数组中删除对应的标题元素,因为 Vue.js 的核心是数据与视图的双向绑定。因此当我们修改数组时, Vue.js 就会检测到数组发生了变化,所以用 v-for 渲染的视图也会同步更新 。
  • 接着,隐藏对应的 tab 内容,我们通过传入的 name 与某个 tab 中的 index,逐一比对,如果确定是我们需要关闭的标签页,那么就隐藏其内容。其实这里使用 key 来表达更合适。

新增的样式:

.close{
 color: #FF6666;
}
.close::before {
 content: "\2716";
}

.close:hover {
 color: #990033;
 font-weight: bolder;
}

为需要添加关闭标签的 pane ,添加 closable 属性:

<div id="app" v-cloak>
 <tabs v-model="activeIndex">
 <pane label="科技" closable="true">
 火星疑似发现“外星人墓地”?至今无法解释
 </pane>
 <pane label="体育">
 全美沸腾!湖人队4年1.2亿迎顶级后卫,詹姆斯:有他就能夺冠
 </pane>
 <pane label="娱乐" closable="true">
 阿米尔汗谈中国武侠 想拍印度版《鹿鼎记》
 </pane>
 </tabs>
</div>

效果:

如何在Vue.js中实现标签页组件详解

3 切换动画

我们在切换标签页时,加上滑动动画吧,这很简单,只要在激活的样式中加上 transform 与 transition 样式即可:

.tabs-tab-active {
 color: #336699;
 border-top: 1px solid #336699;
 border-bottom: 1px solid #fff;
 transform:translateY(-1px);
 transition: transform 0.5s;
}

效果:

如何在Vue.js中实现标签页组件详解

我们让标签页标题被点击时,以动画的形式往上移动 1 个像素。是不是很酷呀O(∩_∩)O~

本文示例代码

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javaScript 读取和设置文档元素的样式属性
Apr 14 Javascript
JavaScript全局函数使用简单说明
Mar 11 Javascript
JavaScript的Module模式编程深入分析
Aug 13 Javascript
使用JavaScript修改浏览器URL地址栏的实现代码
Oct 21 Javascript
一起学写js Calender日历控件
Apr 14 Javascript
jQuery插件pagination实现无刷新分页
May 21 Javascript
AngularJS执行流程详解
Feb 17 Javascript
基于JS递归函数细化认识及实用实例(推荐)
Aug 07 Javascript
js中DOM事件绑定分析
Mar 18 Javascript
jQuery基于Ajax实现读取XML数据功能示例
May 31 jQuery
解决angularjs service中依赖注入$scope报错的问题
Oct 02 Javascript
AJAX在JQuery中的应用详解
Jan 30 jQuery
如何使用less实现随机下雪动画详解
Jan 02 #Javascript
详解Vue2 添加对scss的支持
Jan 02 #Javascript
详解@Vue/Cli 3 Invalid Host header 错误解决办法
Jan 02 #Javascript
JS中数据结构之栈
Jan 01 #Javascript
微信小程序自定义导航栏
Dec 31 #Javascript
JavaScript ES6中的简写语法总结与使用技巧
Dec 30 #Javascript
JavaScript ES6箭头函数使用指南
Dec 30 #Javascript
You might like
咖啡冲泡指南 咖啡有哪些制作方式 单品咖啡 意式咖啡
2021/03/06 冲泡冲煮
thinkphp 多表 事务详解
2013/06/17 PHP
php实现二进制和文本相互转换的方法
2015/04/18 PHP
ThinkPHP实现附件上传功能
2017/04/27 PHP
php xhprof使用实例详解
2019/04/15 PHP
学习ExtJS Column布局
2009/10/08 Javascript
getJSON调用后台json数据时函数被调用两次的原因猜想
2013/09/29 Javascript
jsonp原理及使用
2013/10/28 Javascript
js使用数组判断提交数据是否存在相同数据
2013/11/27 Javascript
JS小游戏之象棋暗棋源码详解
2014/09/25 Javascript
html的DOM中document对象anchors集合用法实例
2015/01/21 Javascript
javascript原始值和对象引用实例分析
2015/04/25 Javascript
jQuery实现指定内容滚动同时左侧或其它地方不滚动的方法
2015/08/08 Javascript
jQuery给指定的table动态添加删除行的操作方法
2016/10/12 Javascript
详解js的事件处理函数和动态创建html标记方法
2016/12/16 Javascript
js实现3d悬浮效果
2017/02/16 Javascript
vue实现微信分享链接添加动态参数的方法
2019/04/29 Javascript
js实现移动端吸顶效果
2020/01/08 Javascript
JS实现随机点名器
2020/04/12 Javascript
利用PHP实现递归删除链表元素的方法示例
2020/10/23 Javascript
antd多选下拉框一行展示的实现方式
2020/10/31 Javascript
[05:26]2014DOTA2西雅图国际邀请赛 iG战队巡礼
2014/07/07 DOTA
Python实现可自定义大小的截屏功能
2018/01/20 Python
python在文本开头插入一行的实例
2018/05/02 Python
OPENCV去除小连通区域,去除孔洞的实例讲解
2018/06/21 Python
python实现随机漫步算法
2018/08/27 Python
浅谈css3中calc在less编译时被计算的解决办法
2017/12/04 HTML / CSS
英国女鞋购物网站:Moda in Pelle
2019/02/18 全球购物
英国屋顶用品和材料超市:Roofing Supplies UK
2019/08/24 全球购物
思想汇报格式
2014/01/05 职场文书
保护环境建议书400字
2014/05/13 职场文书
我为党旗添光彩演讲稿
2014/09/13 职场文书
上课睡觉万能检讨书
2015/02/17 职场文书
就业推荐表自我评价范文
2015/03/02 职场文书
环保主题班会教案
2015/08/13 职场文书
教师旷工检讨书
2015/08/15 职场文书