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>
        
        <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>
    

    你可能感兴趣的:(Vue开发)