better-scroll实现菜单和列表滚动效果联动。

上一篇中,我们使用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>
        <!-- ...hook表示仅用于js操作选择,无表达效果。 -->
        <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传入函数。

        <li v-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>
        <!-- ...hook表示仅用于js操作选择,无表达效果。 -->
        <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>

猜你喜欢

转载自blog.csdn.net/SilenceJude/article/details/82383859