选项卡在 web 中的应用可以说是无处不在。因此掌握它的实现对我们来说非常有用。

通常情况下,选项卡由两部分组成。一部分是头部,它包含一堆按钮,每一个按钮对应不同的页面,按钮包括选中,与无法选中两种状态。另一部分则由一堆具体的页面组成。当我们点击按钮时,切换到对应的页面。

INFO

如果每个页面中包含的是根据动态加载的数据渲染出来的界面,那么通常只会有一个页面,点击按钮时重新加载数据并重新渲染页面。

我们先在 html 中将这两部分代码写出来。

code.ts
1
<div class="box" id="tab_wrap">
2
<ul class="tab_options">
3
<li data-index="0" class="item active">tab1</li>
4
<li data-index="1" class="item">tab2</li>
5
<li data-index="2" class="item">tab3</li>
6
<li data-index="3" class="item">tab4</li>
7
</ul>
8
<div class="tab_content">
9
<div class="item_box active">tab box 1</div>
10
<div class="item_box">tab box 2</div>
11
<div class="item_box">tab box 3</div>
12
<div class="item_box">tab box 4</div>
13
</div>
14
</div>

并简单写一些css代码。

code.ts
1
body {
2
margin: 0;
3
}
4
5
ul, li {
6
list-style: none;
7
padding: 0;
8
}
9
10
#tab_wrap {
11
max-width: 400px;
12
margin: 10px auto;
13
background: #efefef;
14
}
15
16
#tab_wrap .tab_options {
17
height: 40px;
18
display: flex;
19
justify-content: space-around;
20
border-bottom: 1px solid #ccc;
21
}
22
#tab_wrap .tab_options li {
23
line-height: 40px;
24
cursor: pointer;
25
}
26
#tab_wrap .tab_options li.active {
27
color: red;
28
border-bottom: 1px solid red;
29
}
30
31
#tab_wrap .tab_content {
32
min-height: 400px;
33
position: relative;
34
}
35
36
#tab_wrap .tab_content .item_box {
37
position: absolute;
38
left: 0;
39
top: 0;
40
width: 100%;
41
display: none;
42
text-align: center;
43
}
44
#tab_wrap .tab_content .item_box.active {
45
display: block;
46
}

选项卡的实现原理非常简单。大家可以注意我们在 html 代码中,每一个头部按钮都保存了一个 data-index 属性。这个属性告诉我们这是第几个按钮。这个值同时也对应第几页。因此我们只需要声明一个 index 变量来保存当前页的序列,并在点击时把当前页的值修改为data-index的值就可以了,与此同时,让当前按钮修改为选中状态,其他按钮修改为未选中状态,让当前页显示,其他页隐藏即可。JavaScript 代码如下。

code.ts
1
var tabHeader = document.querySelector('.tab_options');
2
var items = tabHeader.children;
3
var tabContent = document.querySelector('.tab_content');
4
var itemboxes = tabContent.children;
5
6
var index = 0;
7
8
tabHeader.addEventListener('click', function(e) {
9
var a = [].slice.call(e.target.classList).indexOf('item');
10
if (a > -1 && index != e.target.dataset.index) {
11
items[index].classList.remove('active');
12
itemboxes[index].classList.remove('active');
13
index = e.target.dataset.index;
14
items[index].classList.add('active');
15
itemboxes[index].classList.add('active');
16
}
17
}, false);

但是此时假设我们要新增一个功能,在html中新增两个按钮,点击他们我们可以分别切换到上一页跟下一页,我们应该怎么办?

code.ts
1
<button class="next">Next</button>
2
<button class="prev">Prev</button>

为了能够更直观的实现这个功能,我们尝试来将选项卡封装成为一个对象。代码如下:

code.ts
1
!function(ROOT) {
2
var index = 0;
3
function Tab(elem) {
4
this.tabHeader = elem.firstElementChild;
5
this.items = this.tabHeader.children;
6
this.tabContent = elem.lastElementChild;
7
this.itemboxes = this.tabContent.children;
8
this.max = this.items.length - 1;
9
10
this.init();
11
}
12
13
Tab.prototype = {
14
constructor: Tab,
15
init: function() {
16
this.tabHeader.addEventListener('click', this.clickHandler.bind(this), false);
17
},
18
clickHandler: function(e) {
19
var a = [].slice.call(e.target.classList).indexOf('item');
20
if (a > -1) {
21
this.switchTo(e.target.dataset.index);
22
}
23
},
24
switchTo: function(i) {
25
if (i == index) {
26
return;
27
}
28
this.items[index].classList.remove('active');
29
this.itemboxes[index].classList.remove('active');
30
index = i;
31
this.items[index].classList.add('active');
32
this.itemboxes[index].classList.add('active');
33
},
34
next: function() {
35
var tgIndex = 0;
36
if (index >= this.max) {
37
tgIndex = 0;
38
} else {
39
tgIndex = index + 1;
40
}
41
this.switchTo(tgIndex);
42
},
43
pre: function() {
44
var tgIndex = 0;
45
if (index == 0) {
46
tgIndex = this.max;
47
} else {
48
tgIndex = index - 1;
49
}
50
this.switchTo(tgIndex);
51
},
52
getIndex: function() {
53
return index;
54
}
55
}
56
57
ROOT.Tab = Tab;
58
}(window);

在上面的代码中,我们将切换功能封装成为了基础的switchTo方法,它接收一个表示页面序列的参数,只要我们调用这个方法,就能够切换到对应的页面。

因此基于这个基础方法,我们就能够很简单的扩展出下一页 next 与上一页 pre 方法。

那么再使用时,就很简单了,代码如下:

code.ts
1
var tab = new Tab(document.querySelector('#tab_wrap'));
2
3
document.querySelector('.next').addEventListener('click', function() {
4
tab.next();
5
console.log(tab.getIndex());
6
}, false);
7
8
document.querySelector('.prev').addEventListener('click', function() {
9
tab.pre();
10
console.log(tab.getIndex());
11
}, false);

这样,一个简单的选项卡功能就实现了。并且拥有了一些简单的扩展功能。从使用中我们可以看到,封装好之后,想要实现什么功能,我们只需要调用对应的接口即可。面向对象封装之后我们的代码变得非常简单直观。

需要注意的是,为了考验大家对于闭包的理解,在上面的代码中,我故意留了一个坑,大家只要在页面中创建两个选项卡,就能够发现问题。建议大家结合闭包思考一下为什么会出现这样的情况。

专栏首页
到顶
专栏目录