您的位置:首页 > 其它

better-scroll插件初使用

2017-07-19 09:34 741 查看


模仿了一个饿了吗页面先上图,看一看better-scroll的效果



如果用overflow:auto或scroll属性,这样的页面就会出现两个滚动条,用户体验会有折扣,所以给介绍一款插件better-scroll,附上github资源链接。这款插件是基于iscroll插件做的重新封装,改善了一些bug,增加了一些拓展功能(插件作者并不是本人,望周知),有兴趣的小伙伴们可以自行下载体验体验。那么今天就用bs插件配合vue框架对这个页面重构一下,主要实现以下几点功能。

- 1、左右侧页面滑动,并且不显示滚动条。 
- 2、根据左侧的商品类别对应到右侧相应的选择区间,并且高亮标题。 
- 3、根据右侧用户滑动的区间,能够对应到左侧的商品类别,并且高亮选择标题。 
ps:除了用到bs插件的一些基础功能以外,还有一些vue的基础知识。


前期准备

我引入了reset.css对页面的样式做一个算是初始化吧,然后引入js脚本有vue.js,vue-resource.js,bscroll.js,可以进入vue官网下载。


页面布局

页面布局和样式就不浪费时间了,直接上代码了

样式代码

[v-cloak] {
display: none;
}

.goods {
position: absolute;
width: 100%;
top: 174px;
bottom: 46px;
display: flex;
overflow: hidden;
}

.goods .menu-wrapper {
flex: 0 0 80px;
width: 80px;
background: #f3f5f7;
}

.goods .menu-wrapper .current {
position: relative;
z-index: 10;
margin-top: -1px;
background: #FFFFFF;
font-weight: 700;
font-size: 14px;
}

.goods .menu-wrapper .menu-item {
display: table;
height: 54px;
width: 80px;
line-height: 14px;
padding: 0 12px;
border-bottom: 1px solid rgba(7, 17, 27, .1);
box-sizing: border-box;
}

.goods .menu-wrapper .menu-item .icon {
display: inline-block;
width: 12px;
height: 12px;
margin-right: 2px;
-webkit-background-size: 12px 12px;
background-size: 12px 12px;
background-repeat: no-repeat;
vertical-align: top;
}

.goods .menu-wrapper .menu-item .text {
display: table-cell;
width: 56px;
vertical-align: middle;
font-size: 12px;
text-align: center;
}

.goods .menu-wrapper .menu-item .decrease {
background-image: url(img/decrease_2@2x.png);
}

.goods .menu-wrapper .menu-item .discount {
background-image: url(img/decrease_2@2x.png);
}

.goods .menu-wrapper .menu-item .guarantee {
background-image: url(img/decrease_2@2x.png);
}

.goods .menu-wrapper .menu-item .invoice {
background-image: url(img/decrease_2@2x.png);
}

.goods .menu-wrapper .menu-item .special {
background-image: url(img/decrease_2@2x.png);
}

.goods .foods-wrapper {
flex: 1;
}

.goods .foods-wrapper .title {
padding-left: 14px;
height: 26px;
line-height: 26px;
border-left: 2px solid #d9dde1;
font-size: 12px;
color: rgb(147, 153, 159);
background: #F3F5F7;
}

.goods .foods-wrapper .current {
color: #42B983;
font-size: 14px;
transition: all .5s;
line-height: 27px;
}

.goods .foods-wrapper .food-item {
display: flex;
margin: 18px 0 18px 0;
border-bottom: 1px solid rgba(7, 17, 27, .1);
padding-bottom: 18px;
}

.goods .foods-wrapper .food-item:last-child {
border-bottom: 0px solid rgba(7, 17, 27, .1);
margin-bottom: 0;
}

.goods .foods-wrapper .food-item .icon {
flex: 0 0 57px;
margin-right: 10px;
margin-left: 10px;
}

.goods .foods-wrapper .food-item .content {
position: relative;
flex: 1;
}

.goods .foods-wrapper .food-item .content .name {
margin: 2px 0 8px 0;
height: 14px;
line-height: 14px;
font-size: 14px;
color: rgb(7, 17, 27);
}

.goods .foods-wrapper .food-item .content .desc {
margin-bottom: 8px;
line-height: 10px;
font-size: 10px;
color: rgb(147, 153, 159);
}

.goods .foods-wrapper .food-item .content .extra {
font-size: 10px;
color: rgb(147, 153, 159);
line-height: 10px;
}

.goods .foods-wrapper .food-item .content .extra .count {
margin-right: 12px;
}

.goods .foods-wrapper .food-item .content .price {
font-weight: 700;
line-height: 24px;
}

.goods .foods-wrapper .food-item .content .price .now {
margin-right: 8px;
font-size: 14px;
color: rgb(240, 20, 20);
}

.goods .foods-wrapper .food-item .content .price .old {
text-decoration: line-through;
font-size: 10px;
color: rgb(147, 153, 159);
}

.goods .foods-wrapper .food-item .content .cartcontrol-wrapper {
position: absolute;
right: 6px;
bottom: 12px;
}
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

页面布局代码

<div class="goods" v-cloak>
<div class="menu-wrapper" ref="menuwrapper">
<ul>
<!--当currentIndex与index相等的时候,设置高亮-->
<li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex === index}" @click="selectMenu(index,$event)" v-cloak>
<span class="text">
<span v-show="item.type>0" class="icon" :class="classMap[item.type]" v-cloak></span> {{item.name}}
</span>
</li>
</ul>
</div>
<div class="foods-wrapper" ref="foodwrapper">
<ul>
<!--food-list-hook用于dom操作,获取整体容器的高度-->
<li v-for="(item,index) in goods" class="food-list food-list-hook" v-cloak>
<h2 class="title" :class="{'current':currentIndex === index}">{{item.name}}</h2>
<ul>
<li @click="selectfood(food,$event)" v-for="food in item.foods" class="food-item">
<div class="icon">
<img :src="food.icon" />
</div>
<div class="content">
<h2 class="name">{{food.name}}</h2>
<p class="desc">{{food.description}}</p>
<div class="extra">
<span class="count">月售{{food.sellCount}}份</span>
<span>好评率{{food.rating}}%</span>
</div>
<div class="price">
<span class="now">¥{{food.price}}</span>
<span v-show="food.oldPrice" class="old">¥{{food.oldPrice}}</span>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
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
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


js代码

首先在data内定义几个变量
goods:[],
listHeight:[],
scrollY:0,
1
2
3
1
2
3
在created中请求准备好的json数据
this.$http.get('./data.json').then((res) => {
if(res.status === ERR_OK) {
res = res.body.goods;
this.goods = res;
}
})
1
2
3
4
5
6
1
2
3
4
5
6
首先我们已经引入了bs插件,我们先让左右两侧的被隐藏的部分滚动起来,在methods方法里面定义一个_initScroll的函数,主要用来对左右两侧dom结构进行初始化。用better-scroll的方法初始化需要滚动的dom结构,vue为我们提供了一个方法可以便利的获取到dom结构,我们在需要获取dom结构的父容器内添加
ref="foodwrapper"
 ,然后在函数内用
this.$refs.menuwrapper
获取到dom。
然后在ajax内执行_initScroll() 函数,这个时候需要注意两点,第一使用bs插件的时候子容器的高度一定要大于父容器的高度,才会产生滚动效果。第二,我们要等dom结构完全加载结束在调用_initScroll()方法才会生效,vue的作者也为我们提供了方法,来判断dom结构是否完全加载
this.$nextTick(()
=> {})
click: true
属性用来设置可以进行点击事件。
_initScroll() {
this.meunScroll = new BScroll(this.$refs.menuwrapper, {
click: true
});
this.foodScroll = new BScroll(this.$refs.foodwrapper, {
click: true
});
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
这时候created应改为
created() {
this.classMap = ['decrease', 'discount', 'guarantee', 'invoice', 'special'];
this.$http.get('./data.json').then((res) => {
if(res.status === ERR_OK) {
res = res.body.goods;
this.goods = res;
//dom结构加载结束
this.$nextTick(() => {
this._initScroll();
})
}
});

},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
下面实现左右联动并且实现文本的高亮,左右联动的基本原理其实我们计算出右侧实时变化的y值,落到哪一个区间,我们就显示那一个区间。首先我们要计算整体区间的一个高度,然后分别计算第一个区间的高度,第二个区间的高度,以此类推。然后将区间数存入一个定义好的数组。当我们在滚动的时候实时拿到y轴的高度,然后对比在哪一个区间,这样我们就会得到一个区间的索引值去对应左侧的菜品类别,最后我们用一个vue的class去绑定高亮文本。
定义一个方法在_initScroll下面,作为计算高度的方法叫做_calculateHeight () ,在定义一个listHeight:[]数组,存放获取的高度。我们在定义一个food-list-hook类,用来被js选择。不要忘记在created内调用函数。
_calculateHeight () {
let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');
let height = 0;
//把第一个高度送入数组
this.listHeight.push(height);
//通过循环foodList下的dom结构,将每一个li的高度依次送入数组
for(let i=0; i<foodList.length; i++){
let item = foodList[i]
height += item.clientHeight
this.listHeight.push(height);
}
},
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
我们获取到区间高度数组后,我们要实时获取到右侧的y值,和左侧的索引值做一个对比,定义一个scrollY变量用来存放实时获取的y值。bs插件为我们提供了一个实时获取y值的方法,我们在初始化
this.foodScroll
的时候加一个·属性
probeType:
3
,其作用就是实时获取y值,相当于探针的作用。
我们在添加一个方法
this.foodScroll.on('scroll',(pos) => {})
,作用是实时滚动的时候把获取到的位置给暴露出来。代码如下。
methods: {
_initScroll() {
this.meunScroll = new BScroll(this.$refs.menuwrapper, {
click: true
});
this.foodScroll = new BScroll(this.$refs.foodwrapper, {
click: true,
//探针作用,实时监测滚动位置
probeType: 3
});
//设置监听滚动位置
this.foodScroll.on('scroll', (pos) => {
//scrollY接收变量
this.scrollY = Math.abs(Math.round(pos.y));
})
},
_calculateHeight() {
let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');
let height = 0;
//把第一个高度送入数组
this.listHeight.push(height);
//通过循环foodList下的dom结构,将每一个li的高度依次送入数组
for(let i = 0; i < foodList.length; i++) {
let item = foodList[i]
height += item.clientHeight
this.listHeight.push(height);
}
},
}
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
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
定义一个计算属性computed,用来计算左侧对应的i值,从而定位到左侧边栏的位置
computed:{
currentIndex () {
for(let i=0; i<this.listHeight.length; i++){
//判断当currentIndex在height1和height2之间的时候显示
let height1 = this.listHeight[i];
let height2 = this.listHeight[i+1];
//最后一个区间没有height2
if(!height2 || (this.scrollY >= height1 && this.scrollY < height2)){
return i;
}
}
return 0;
},
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
获取到i后,在menu-item绑定一个class
:class="{'current':currentIndex === index}"
,当currentIndex和menu-item对应的index相等时,设置current的样式。这样就可以左右联动了。
最后实现左侧点击的功能。在左侧的li下绑定一个selectMenu的点击事件,并传入索引值,这样我们就可以知道点击的是哪一个li
selectMenu (index,event) {
//      自己默认派发事件时候(BScroll),_constructed被置为true,但是浏览器原生并没有这个属性
if (!event._constructed){
return;
}
//运用BScroll接口,滚动到相应位置
let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');
//获取对应元素的列表
let el = foodList[index];
//设置滚动时间
this.foodScroll.scrollToElement(el, 300);
},
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
至此,我们就用bs插件完成了这个左右页面联动的效果,代码会上传到github,有兴趣的可以下载下来看一看,大神务喷!
完整的js代码
<script type="text/javascript">
var ERR_OK = 200;
new Vue({
el: '.goods',
data() {
return {
msg: 'goods',
goods: [],
listHeight: [],
scrollY: 0,
}
},
created() {
this.classMap = ['decrease', 'discount', 'guarantee', 'invoice', 'special'];
this.$http.get('./data.json').then((res) => {
if(res.status === ERR_OK) {
res = res.body.goods;
this.goods = res;
//dom结构加载结束
this.$nextTick(() => {
this._initScroll();
//计算高度
this._calculateHeight();
})
}
});

},
computed: {
currentIndex() {
for(let i = 0; i < this.listHeight.length; i++) {
//判断当currentIndex在height1和height2之间的时候显示
let height1 = this.listHeight[i];
let height2 = this.listHeight[i + 1];
//          console.log('height1:'+height1+','+'height2:'+height2)
//最后一个区间没有height2
if(!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
return i;
}
}
return 0;
}
},
methods: {
selectMenu(index, event) {
//      自己默认派发事件时候(BScroll),_constructed被置为true,但是浏览器原生并没有这个属性
if(!event._constructed) {
return;
}
//运用BScroll接口,滚动到相应位置
let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');
//获取对应元素的列表
let el = foodList[index];
this.foodScroll.scrollToElement(el, 300);
},
_initScroll() {
this.meunScroll = new BScroll(this.$refs.menuwrapper, {
click: true
});
this.foodScroll = new BScroll(this.$refs.foodwrapper, {
click: true,
//探针作用,实时监测滚动位置
probeType: 3
});
//设置监听滚动位置
this.foodScroll.on('scroll', (pos) => {
//scrollY接收变量
this.scrollY = Math.abs(Math.round(pos.y));
})
},
_calculateHeight() {
let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');
let height = 0;
//把第一个高度送入数组
this.listHeight.push(height);
//通过循环foodList下的dom结构,将每一个li的高度依次送入数组
for(let i = 0; i < foodList.length; i++) {
let item = foodList[i]
height += item.clientHeight
this.listHeight.push(height);
}
},
}
})
</script>
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


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

完整项目效果演示


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: