前言
在前端开发中,我们经常会遇到样式切换操作。
在浏览器环境下,使用原生JS或jQuery可以很方便的对dom进行操作,从而达到基本的样式切换效果。但是在微信小程序中没有dom,我们不能直接对dom进行操作。
这应该会让之前一直在浏览器环境开发且没有接触过数据驱动类框架的朋友很不习惯,每当遇到样式切换操作时,清除其他样式,给当前点击元素添加样式的思想已经深入骨髓,这种情况下去接触小程序,确实一时半会转不过神来。
现在就由我来以二级菜单为例,给大家简单介绍一下基于数据驱动的样式切换该怎样实现。
这是完成效果图
选项卡菜单
在做二级菜单之前,我们从简单的开始,先来做一个最常见的一级选项卡菜单
代码部分
首先是wxml部分1
2
3
4
5<view class='nav'>
<view class='{{num==0?"active":""}}' data-num='0' catchtap='changeClass'>22</view>
<view class='{{num==1?"active":""}}' data-num='1' catchtap='changeClass'>33</view>
<view class='{{num==2?"active":""}}' data-num='2' catchtap='changeClass'>233</view>
</view>
然后是样式部分,选中时字体变红1
2
3
4
5
6
7
8
9
10
11
12
13
14
15.active{
color:red;
}
.nav {
display: flex;
flex-direction: row;
margin: 20rpx 0 0 0;
}
.nav view {
flex: 1;
text-align: center;
font-weight: bold;
line-height: 40rpx;
padding: 20rpx 0;
}
js部分1
2
3
4
5
6changeClass:function(e){
console.log(e);
this.setData({
num: e.target.dataset.num;
})
}
解释一下思路
思路选择
总结一下我们在浏览器环境下的样式切换思路
- 通过js查找到元素然后修改其class名
- 添加一个class名,覆盖前面的样式
- 修改行内样式,覆盖其他的样式
这个思路在小程序里同样适用,只是实现方法稍微有点不一样
修改class名
1
2<view class="{{className}}"></view>
//{{}}内的className为变量,js里通过setData方法修改这个变量,页面会实时刷新,再也不用操作dom节点查找元素啦~添加一个class2,覆盖前面的class1
1
<view class="class1 {{class2}}"></view>
修改行内样式,覆盖其他的样式
1
<view class="class1" style="{{newStyle}}"></view>
方法一和方法二实际上非常像,推荐使用这两种。
详细说明
接下来我们回到最先写的wxml上面,双花括号内是支持简单运算的,所以使用三目运算符也没问题。这里的num
对应js文件里面data
的变量,相当于1
2
3data: {
num: undefined, //只声明不初始化的情况下,变量的初始值为undefined
},
WXML 中的动态数据均来自对应 Page 的 data。
当num==0
时,这里元素的class
名就会变为active
,否则为空。因此我们的目的就是切换num值来改变样式。那么怎么切换num
值呢?
我们接着看,会发现data-num='0'
这个属性,在微信小程序中,允许通过以data-
开头的方式来自定义变量,这里面的数据会传到事件对象中,在本例中,可以在点击事件触发后,在e.target.dataset.num
的里面获取到自定义的data-num
值。
1 | e //事件对象,通过事件函数的一个参数传递,可以是任何值 |
最后我们通过catchtap
事件,利用微信小程序自带的this.setData
方法修改num
的值为当前点击
的元素里定义的data-num
值。也就是num的值==当前点击的元素num时,class名改变1
2
3this.setData({
num: e.target.dataset.num;
})
当我们点击一个菜单时,触发catchtap
事件,num值改变。三目运算符触发判断,相等时就会返回active
。
这样,利用数据绑定的形式修改样式的效果就达到了。
接下来进行二级菜单的代码书写
二级菜单
废话不多说,先上代码
代码部分
wxml部分1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<view class="top-select">
<view class="fl-row">
<block wx:for="{{menuList}}" wx:for-item="listItem" wx:for-index="listIndex">
<!--//template 一级菜单-->
<view bindtap="changeClass" class="item {{listItem.isChecked?'active':''}}" data-num="{{listItem.listId}}">
<view>
<text>{{listItem.title}}</text>
<text class="angle"><text class="iconfont icon-xiangxiazhankai"></text></text>
</view>
</view>
</block>
</view>
<view bindtap="changeClass2">
<block wx:for="{{menuList}}" wx:for-item="listItem">
<!--//template 二级菜单-->
<view class="show-list" wx:if="{{listItem.isChecked}}">
<view wx:for="{{listItem.item}}" wx:for-item="childItem" class="{{goodsTitle==childItem.Name?'select':''}}" data-name="{{childItem.Name}}">
{{childItem.Name}}
</view>
</view>
</block>
</view>
</view>
wxss部分1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85/*折叠菜单 */
.fl-row {
display: flex;
flex-direction: row;
margin: 20rpx 0 0 0;
}
.top-select .item {
flex: 1;
text-align: center;
font-weight: bold;
line-height: 40rpx;
padding: 20rpx 0;
}
.top-select .item text {
font-size: 30rpx;
}
.top-select .item .angle {
font-size: 30rpx;
}
.active {
background-color: #229bc9;
}
.active text {
color: #fff;
}
/* 倒三角箭头动画,使用css动画性能更好,缺点是首次加载时,会看到一次复原动画*/
.angle{
display: inline-block;
animation: 0.3s rotatereturn forwards;
}
.active .angle {
display: inline-block;
animation: 0.3s rotatestart forwards;
}
@keyframes rotatestart {
from {
transform: rotate(0deg) ;
}
to {
transform: rotate(180deg) ;
transition: 0.3s;
}
}
@keyframes rotatereturn {
from {
transform: rotate(180deg);
}
to {
transform: rotate(0deg);
transition: 0.3s;
}
}
/*菜单子项 */
.show-list {
background: #229bc9;
padding-bottom: 20rpx;
color: #fff;
}
.show-list view {
display: inline-block;
font-size: 26rpx;
padding: 20rpx;
padding-bottom: 10rpx;
}
.actived {
display: block;
}
.select {
color: #f72424;
}
js部分1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
onLoad: function (options) {
var menuList = []; //后台获取的menuList数组(菜单数据)
//后台获取的menuList绑定到data里面,方便wxml读取。
this.setData({
menuList: menuList;
})
},
// 一级列表点击事件
changeClass: function (e) {
// 获取当前点击元素的num值
var idx = e.currentTarget.dataset.num;
// 读取已绑定过的后台菜单数据menuList
var menuList = this.data.menuList;
// 给一级菜单添加自定义属性isChecked,用来记录是否点击状态
for (var i = 0; i < menuList.length; i++) {
var Item = menuList[i];
// 如果菜单的索引值listId==点击元素的num值
if(Item.listId == idx){
// 一级菜单自定义属性isChecked取反
Item.isChecked = !Item.isChecked;
}
// 否则一级菜单自定义属性isChecked初始化为false
else{
Item.isChecked = false;
}
}
this.setData({
menuList: menuList, //更新isChecked的状态
})
},
// 二级列表点击事件
changeClass2 : function(e){
this.setData({
//当前点击的二级子菜单每个元素的自定义name值,动态更新给goodsTitle
goodsTitle: e.target.dataset.name
})
}
菜单数据部分1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61var menuList = [
{
title: "产品",
listId: 0,
item: [
{ ID: 0, Name: "产品0", Type: 1 },
{ ID: 1, Name: "产品1", Type: 1 },
{ ID: 2, Name: "产品2", Type: 1 },
{ ID: 3, Name: "产品3", Type: 1 },
{ ID: 4, Name: "产品4", Type: 1 },
{ ID: 5, Name: "产品5", Type: 1 },
{ ID: 6, Name: "产品6", Type: 1 },
{ ID: 7, Name: "产品7", Type: 1 },
{ ID: 8, Name: "产品8", Type: 1 },
{ ID: 9, Name: "产品9", Type: 1 }]
},
{
title: "风格",
listId: 1,
item: [
{ ID: 0, Name: "风格0", Type: 2 },
{ ID: 1, Name: "风格1", Type: 2 },
{ ID: 2, Name: "风格2", Type: 2 },
{ ID: 3, Name: "风格3", Type: 2 },
{ ID: 4, Name: "风格4", Type: 2 },
{ ID: 5, Name: "风格5", Type: 2 },
{ ID: 6, Name: "风格6", Type: 2 },
{ ID: 7, Name: "风格7", Type: 2 }]
},
{
title: "实景案例",
listId: 2,
item: [
{ ID: 0, Name: "实景案例0", Type: 3 },
{ ID: 1, Name: "实景案例1", Type: 3 },
{ ID: 2, Name: "实景案例2", Type: 3 },
{ ID: 3, Name: "实景案例3", Type: 3 },
{ ID: 4, Name: "实景案例4", Type: 3 },
{ ID: 5, Name: "实景案例5", Type: 3 },
{ ID: 6, Name: "实景案例6", Type: 3 },
{ ID: 7, Name: "实景案例7", Type: 3 },
{ ID: 8, Name: "实景案例8", Type: 3 },
{ ID: 9, Name: "实景案例9", Type: 3 },
{ ID: 10, Name: "实景案例10", Type: 3 }]
},
{
title: "ICC产品",
listId: 3,
item: [
{ ID: 0, Name: "ICC产品0", Type: 4 },
{ ID: 1, Name: "ICC产品1", Type: 4 },
{ ID: 2, Name: "ICC产品2", Type: 4 },
{ ID: 3, Name: "ICC产品3", Type: 4 },
{ ID: 4, Name: "ICC产品4", Type: 4 },
{ ID: 5, Name: "ICC产品5", Type: 4 },
{ ID: 6, Name: "ICC产品6", Type: 4 },
{ ID: 7, Name: "ICC产品7", Type: 4 },
{ ID: 8, Name: "ICC产品8", Type: 4 }]
}
]
代码解析
大家应该发现了,一级菜单时代码还很少,二级菜单时代码突然变得多了起来,别急,我们一步步慢慢来。
第一个例子一级菜单,比较适用于菜单选项固定不变的场景,但是实际开发中,我们常常需要动态改变菜单个数和菜单选项,因此菜单数据需要从后台加载获取。
第一级菜单
先从wxml
部分说起,看一级菜单部分,这里会看到新标签block
,block
是一个不会实际渲染出来的标签,主要用来结合wx:for
或wx:if
之类的表达式来进行条件渲染被block
包住的元素。
在组件上使用
wx:for
控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组的当前项的下标变量名wx:for-index
值默认为index
,数组当前项的变量名wx:for-item
值默认为item
wx:for-index="idx"
wx:for-item="itemName"
可以修改对应的变量名
1 | wx:for="{{menuList}}" |
block
里的元素1
2
3
4
5
6
7
8bindtap="changeClass"
//一级菜单改变类名的事件
class="item {{listItem.isChecked?'active':''}}"
//三目运算符 判断是否切换为active
data-num="{{listItem.listId}}"
//自定义的num值,实际值来源于menuList每个子项listItem的listId,用来传递到点击事件event对象里
{{listItem.title}}
// menuList每个子项listItem的title
一级菜单点击事件发生时,会触发changeClass
里的事件(具体过程js代码内有详细说明),当前点击的listItem.isChecked
会变为true
,class
名就会设置为active
,二级菜单wx:if
满足条件,执行渲染。这样,一级菜单切换样式的效果就完成了。
第二级菜单
block
里的元素1
2
3
4
5
6
7
8
9
10wx:if="{{listItem.isChecked}}"
//条件渲染,如果listItem.isChecked为真才渲染
wx:for="{{listItem.item}}"
//要循环渲染的数组为menuList里listItem的item,
wx:for-item="childItem"
//listItem.item里的每个子项命名为childItem,
class="{{goodsTitle==childItem.Name?'select':''}}"
//goodsTitle是当前点击的元素的name值,是否和元素自带的name相等
data-name="{{childItem.Name}}"
// 自定义的name值,实际值来源于childItem的Name,用来传递到点击事件event对象里
当二级菜单里的点击事件发生时,用event
对象获取到当前点击的元素自定义的name
值,然后用this.setData
赋值给goodsTitle
,
判断goodsTitle
是否和原数据里自带Name
值相等,是则样式设置为select
。
这样,二级菜单切换样式的效果也完成了。