如果用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>
<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>
<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代碼
goods:[],
listHeight:[],
scrollY:0,
this.$http.get('./data.json').then((res) => {
if(res.status === ERR_OK) {
res = res.body.goods;
this.goods = res;
}
})
- 首先我們已經引入了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
});
}
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();
})
}
});
},
- 下面實現左右聯動並且實現文本的高亮,左右聯動的基本原理其實我們計算出右側實時變化的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);
for(let i=0; i<foodList.length; i++){
let item = foodList[i]
height += item.clientHeight
this.listHeight.push(height);
}
},
- 我們獲取到區間高度數組後,我們要實時獲取到右側的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++){
let height1 = this.listHeight[i];
let height2 = this.listHeight[i+1];
if(!height2 || (this.scrollY >= height1 && this.scrollY < height2)){
return i;
}
}
return 0;
},
- 獲取到i後,在menu-item綁定一個class
:class="{'current':currentIndex === index}"
,當currentIndex和menu-item對應的index相等時,設置current的樣式。這樣就可以左右聯動了。 - 最後實現左側點擊的功能。在左側的li下綁定一個selectMenu的點擊事件,並傳入索引值,這樣我們就可以知道點擊的是哪一個li
selectMenu (index,event) {
if (!event._constructed){
return;
}
let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');
let el = foodList[index];
this.foodScroll.scrollToElement(el, 300);
},
- 至此,我們就用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
完整項目效果演示