上一篇中,我们使用better-scroll实现了列表滚动效果,但是并没有与菜单栏关联起来,这里我们就实现左右联动功能。
解决思路:右侧滚动的时候,可以通过获取右侧列表栏实时的Y轴坐标,然后判断Y轴坐标落在某一个区间,记录下该区间的数组下标,判断左侧高亮的数组索引下标,绑定一个动态类赋予给标的菜单数组索引即可。
首先拿到数据之后,在nextTick中更新,定义一个方法_calculateHeight()来计算高度坐标值。
created () {
this.$http.get('/api/goods').then((response) => {
response = response.body
if (response.errno === ERR_OK) {
this.goods = response.data
this.$nextTick(() => { // vue是异步更新,必须加$nextTick,才能在nextTick里面更新。
this._initScroll()
this._calculateHeight() // 计算food分类中每一个li层的坐标值并放进数组listHeight
})
}
})
},
然后在data(){}中定义一个数组变量listHeight[]储存高度值。
data () {
return {
goods: [], // 获取data.json中的goods数据,json里面是数组形式。
listHeight: [], // 储存每一个food分类下的Y轴高度坐标值。
scrollY: 0 // 记录右侧foods列表实时滚动的Y坐标值。
}
},
为了取得doom元素,我们在food-list列表中定义一个class叫做food-list-hook,仅用于js操作选择,无表达效果
<div class="foods-wrapper" ref="foodsWrapper">
<ul>
<li v-for="(item,index) of goods" class="food-list food-list-hook" :key="index">
<h1 class="title">{{item.name}}h1>
下面在_calculateHeight()中获取food-list-hook的doom元素,开始计算food列表的高度坐标值,并为listHeight[]赋值。
methods: {
_calculateHeight () { // 计算food分类中每一个li层的坐标值并放进数组listHeight
let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook')// 获取food-list-hook类的doom元素。
let height = 0 // 临时变量,用于储存food分类的坐标值。
this.listHeight.push(height)
for (let i = 0; i < foodList.length; i++) {
height = height + foodList[i].clientHeight // 获得每一个food分类的高度数值,将其相加得到每一个food分类的坐标值。
this.listHeight.push(height) // 将每一个food分类的坐标值推入数组。
}
}
}
在data(){}中定义一个数值变量记录右侧foods列表实时滚动的Y坐标值。
data () {
return {
goods: [], // 获取data.json中的goods数据,json里面是数组形式。
listHeight: [], // 储存每一个food分类下的Y轴高度坐标值。
scrollY: 0 // 记录右侧foods列表实时滚动的Y坐标值。
}
},
接下来通过better-scroll中的接口,获取实时的Y坐标值。
_initScroll () {
this.menuScroll = new BScroll(this.$refs.menuWrapper, { // 初始化better-scroll
click: true // better-scroll清除了原来的click,重新添加。
})
this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
probeType: 3 // probeType: 3表示实时传递滚动位置,相当于探针。用法见better-scroll,还可以为1,2,效果不一样。
})
this.foodsScroll.on('scroll', (pos) => { // 实时获得滚动轴的Y坐标。
this.scrollY = Math.abs(Math.round(pos.y))
})
},
拿到了实时Y坐标之后,需要将其同左侧索引做一个映射。这里我们定义一个计算属性,在computed中定义一个变量currentIndex(),表示当前索引应该在哪里。
computed: {
currentIndex () { // 判断foods列表滚动的坐标Y处于哪个区间,返回对应的下标i。
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)) { // !height2用于判断i值最大的情况,此时height=undefined,取!值返回i。
return i
}
}
return 0 // i=0的情况,什么都没有。
}
},
最后给menu菜单绑定一个动态的class:current,当currentIndex的值等于索引的值时,才会赋予这个current类。用current类来显示联动效果。
<div class="goods">
<div class="menu-wrapper" ref="menuWrapper">
<ul>
<li v-for="(item,index) of goods" :key="index" class="menu" :class="{'current':currentIndex === index}" @click="selectMenu(index)">
<span class="text border-1px">
<icon :size="3" :type="item.type" v-show="item.type>0" class="icon">icon>
{{item.name}}
span>
li>
ul>
div>
编写current的css,这样就实现了联动功能。
.menu-wrapper
flex: 0 0 80px
width: 80px
background: #f3f5f7
.menu
display: table
width: 56px
height: 54px
padding: 0 12px
line-height: 14px
&.current
position: relative
z-index: 10
top: -1px
background: #fff
font-weight: 700
.text
border-none()
接下来实现左侧点击,右侧实时滚动的功能。这里在菜单栏绑定一个点击事件@click=”selectMenu(index),将被点击的索引index传入函数。
for="(item,index) of goods" :key="index" class="menu" :class="{'current':currentIndex === index}" @click="selectMenu(index)">
selectMenu()函数在methods中,这样就实现了点击事件的联动滚动效果。
methods: {
selectMenu (index) { // 发生点击事件时,将被点击菜单栏的索引传递过来,与右侧food列表区域绑定,实现滚动。
console.log(index)
let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook') // 获取food列表区间数组集合。
let el = foodList[index] // 获取当前点击菜单栏对应的food列表区间,
this.foodsScroll.scrollToElement(el, 300) // 调动接口函数,使得food列表实时滚动到el所在区间,300表示滚动时间。
},
}
前面初始化menu的滚动时,需要添加click:true属性,否则无法点击。
_initScroll () {
this.menuScroll = new BScroll(this.$refs.menuWrapper, { // 初始化better-scroll
click: true
})
完整代码如下:
<template>
<div class="goods">
<div class="menu-wrapper" ref="menuWrapper">
<ul>
<li v-for="(item,index) of goods" :key="index" class="menu" :class="{'current':currentIndex === index}" @click="selectMenu(index)">
<span class="text border-1px">
<icon :size="3" :type="item.type" v-show="item.type>0" class="icon">icon>
{{item.name}}
span>
li>
ul>
div>
<div class="foods-wrapper" ref="foodsWrapper">
<ul>
<li v-for="(item,index) of goods" class="food-list food-list-hook" :key="index">
<h1 class="title">{{item.name}}h1>
<ul>
<li v-for="(food,index) of item.foods" :key="index" class="food-item border-1px">
<div class="icon">
<img width="57px" height="57px" :src="food.icon">
div>
<div class="content">
<h2 class="name">{{food.name}}h2>
<p v-show="food.description" class="description">{{food.description}}p>
<div class="data">
<span class="count">月售{{food.sellCount}}份span>
<span class="rating">好评率{{food.rating}}%span>
div>
<div class="price">
<span class="new-price">¥{{food.price}}span>
<span v-show="food.oldPrice" class="old-price">¥{{food.oldPrice}}span>
div>
div>
li>
ul>
li>
ul>
div>
div>
template>
<script type='text/ecmascript-6'>
import BScroll from 'better-scroll'
import icon from '../support-icon/icon'
const ERR_OK = 0
export default {
props: {
seller: {
type: Object
}
},
data () {
return {
goods: [], // 获取data.json中的goods数据,json里面是数组形式。
listHeight: [], // 储存每一个food分类下的Y轴高度坐标值。
scrollY: 0 // 右侧foods列表实时滚动的Y坐标值。
}
},
created () {
this.$http.get('/api/goods').then((response) => {
response = response.body
if (response.errno === ERR_OK) {
this.goods = response.data
this.$nextTick(() => { // vue是异步更新,必须加$nextTick,才能在nextTick里面更新。
this._initScroll()
this._calculateHeight() // 计算food分类中每一个li层的坐标值并放进数组listHeight
})
}
})
},
components: {
icon
},
computed: {
currentIndex () { // 判断foods列表滚动的坐标Y处于哪个区间,返回对应的下标i。
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)) { // !height2用于判断i值最大的情况,此时height=undefined,取!值返回i。
return i
}
}
return 0
}
},
methods: {
selectMenu (index) { // 发生点击事件时,将被点击菜单栏的索引传递过来,与右侧food列表区域绑定,实现滚动。
console.log(index)
let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook') // 获取food列表区间数组集合。
let el = foodList[index] // 获取当前点击菜单栏对应的food列表区间,
this.foodsScroll.scrollToElement(el, 300) // 调动接口函数,使得food列表实时滚动到el所在区间,300表示滚动时间。
},
_initScroll () {
this.menuScroll = new BScroll(this.$refs.menuWrapper, { // 初始化better-scroll
click: true
})
this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
probeType: 3 // probeType: 3表示实时传递滚动位置,相当于探针。用法见better-scroll,还可以为1,2,效果不一样。
})
this.foodsScroll.on('scroll', (pos) => { // 实时获得滚动轴的Y坐标。
this.scrollY = Math.abs(Math.round(pos.y))
})
},
_calculateHeight () { // 计算food分类中每一个li层的坐标值并放进数组listHeight
let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook')// 获取food-list-hook类的doom元素。
let height = 0 // 临时变量,用于储存food分类的坐标值。
this.listHeight.push(height)
for (let i = 0; i < foodList.length; i++) {
height = height + foodList[i].clientHeight // 获得每一个food分类的高度数值,将其相加得到每一个food分类的坐标值。
this.listHeight.push(height) // 将每一个food分类的坐标值推入数组。
}
}
}
}
script>
<style lang='stylus' rel='stylesheet/stylus'>
@import '../../common/stylus/mixin.styl'
.goods
display: flex
position: absolute
top: 174px
bottom: 56px
width: 100%
overflow: hidden
.menu-wrapper
flex: 0 0 80px
width: 80px
background: #f3f5f7
.menu
display: table
width: 56px
height: 54px
padding: 0 12px
line-height: 14px
&.current
position: relative
z-index: 10
top: -1px
background: #fff
font-weight: 700
.text
border-none()
.text
display: table-cell
width: 56px
vertical-align: middle
font-size: 12px
border-1px(rgba(7, 17, 27, 0.1))
.icon
margin-right: 2px
.foods-wrapper
flex: 1
.title
padding-left: 14px
font-size: 12px
height: 26px
line-height: 26px
border-left: 2px solid #d9dde1
font-size: 12px
font-weight: 700
color: rgb(147, 153, 159)
background-color: #f3f5f7
.food-item
display: flex
padding: 18px
border-1px(rgba(7, 17, 27, 0.1))
&:last-child
border-none()
.icon
flex: 0 0 57px
margin-right: 10px
.content
flex: 1
font-size: 0
.name
margin: 2px 0 8px 0
height: 14px
line-height: 14px
color: rgb(7, 17, 27)
font-size: 14px
font-weight: 700
.description
display: inline-block
margin-bottom: 8px
line-height: 12px
color: rgb(147, 153, 159)
font-size: 10px
.data
height: 10px
line-height: 10px
font-size: 0
color: rgb(147, 153, 159)
.count
font-size: 10px
margin-right: 12px
.rating
font-size: 10px
.price
height: 24px
.new-price
margin-right: 8px
line-height: 24px
font-size: 14px
color: #f01414
font-weight: 700
.old-price
line-height: 24px
font-size: 10px
color: rgb(147, 153, 159)
font-weight: normal
text-decoration: line-through
style>