(八)在vue中使用better-scroll,实现内容区域的滚动效果,以及右边与左边内容相对应,左边呈现高亮状态

better-scroll.js是对isScroll.js的重构,isScroll.js的github地址是:isScroll,better-scroll.js的文档手册地址是:better-scroll

下面讲讲如何在项目中使用better-scroll

1、首先在项目的根目录下安装better-scroll,better-scroll是支持npm安装的,在cmd中输入下面的语句

npm install better-scroll --save

安装好之后会在package.json文件的dependencies里面多了个better-scroll,表示安装成功,如下图所示


2、在要引用该插件的.vue文件的<script>标签的开头将该插件引进来,然后就可以在项目的任何文件里面引用该插件提供的一些方法以及属性了

import BScroll from 'better-scroll'


在开发的时候可以参照better-scroll的文档手册进行开发相应的功能,地址是:better-scroll文档手册


一:下面讲个实例:文件名是goods.vue,在固定高度的区间内实现内容的上下滑动,下图是效果图



1、下面是goods.vue文件里面的<template>代码,我们要对.menu-wrapper对应上图红圈部分和.food-wrapper的内容对应黄圈部分,实现可以上下滑动的效果

<template>
  <div class="goods">
    <div class="menu-wrapper" ref="menuWrapper">
      <ul>
        <li v-for="(item,index) in goods" class="menu-item">
          <span class="text border-1px">
            <span v-show="item.type > 0" class="icon" :class="classMap[item.type]"></span>{{item.name}}
          </span>
        </li>
      </ul>
    </div>
    <div class="foods-wrapper" ref="foodsWrapper">
      <ul>
        <li v-for="item in goods" class="food-list">
          <h1 class="title">{{item.name}}</h1>
          <ul>
            <li v-for="food in item.foods" class="food-item border-1px">
              <div class="icon">
                <img :src="food.icon" width="57" height="57">
              </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>
</template>

2、下面是goods.vue文件中的<style>内容,在这里要注意几点

1)要给父容器一个固定的高度,当子元素的高度超过父元素的高度的时候就可以实现上下滑动的效果了,这里给父元素.goods设置的高度是采用设置display:absolute;top:174px;bottom:46px从而实现固定高度的

2)给父元素设置一个overflow:hidden属性,当子元素的内容的高度超过父元素的高度的时候,就会把超出的部分给隐藏起来

3)给父元素设定一个固定的宽度

<style lang="stylus">
  @import '../../common/stylus/mixin.styl'
  .goods
    display:flex    /*设置弹性布局*/
    position:absolute /*设定absolute+top+bottom那么该元素就是一个固定高度的容器了*/
    top:174px  /*这个是header+tab组件的高度*/
    width:100%
    overflow:hidden
    bottom:46px /*这个是给底部的购物车留出来的高度*/
    .menu-wrapper
      flex:0 0 80px  /*设置左边的元素的宽度为固定值*/
      width:80px
      background: #f3f5f7
      .menu-item
        display:table
        height:54px
        width:56px
        padding:0 12px
        line-height:14px
        .icon
          display: inline-block
          vertical-align:top
          width:12px
          height:12px
          margin-right :2px
          background-size:12px 12px
          background-repeat :no-repeat
          &.decrease
            bg-image('decrease_3')
          &.discount
            bg-image('discount_3')
          &.guarantee
            bg-image('guarantee_3')
          &.invoice
            bg-image('invoice_3')
          &.special
            bg-image('special_3')
        .text
          display:table-cell
          width:56px
          font-size:12px
          vertical-align: middle
          border-1px(rgba(7,17,27,0.1))
    .foods-wrapper
      flex:1    /*右边的元素的宽度是一个自适应的*/
      .title
        padding-left:14px
        height:26px
        line-height:26px
        border-left: 2px solid #d9dde1
        font-size:12px
        color:rgb(147,153,159)
        background:#f3f5f7
      .food-item
        display:flex
        margin:18px
        padding-bottom:18px
        border-1px(rgba(7,17,27,0.1))
        &:last-child
          border-none()
          margin-bottom:0
        .icon
          flex:0 0 57px
          margin-right:10px
        .content
          flex:1
          .name
            margin:2px 0 8px 0
            height:14px
            line-height:14px
            font-size:14px
            color:rgb(7,17,27)
          .desc,.extra
            line-height:10px
            font-size:10px
            color:rgb(147,153,159)
          .desc
            margin-bottom:8px
            line-height:12px
          .extra
            .count
              margin-right:12px
          .price
            font-weight:700
            line-height:24px
            .now
              margin-right:8px
              font-size:14px
              color:rgb(240,20,20)
            .old
              text-decoration:line-through
              font-size:10px
              color:rgb(147,153,159)
</style>

3、下面是goods.vue文件中的<script>标签里面的内容

主要是有两步:在methods属性里面定义一个better-scroll的实例初始化函数;在created()函数里面用vm.$nextTick()中初始化这个实例

1)在methods属性里面定义一个better-scroll的实例初始化函数

methods:{
        _initScroll(){
          //初始化需要滚动的对象,通过vm.$refs可以获取到在<template>中设置ref=menuWrapper属性的元素节点
          this.menuScroll = new BScroll(this.$refs.menuWrapper,{
          });
          this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
          });
        } 
}

2)在created()函数里面用vm.$nextTick()中初始化这个实例

created(){//在这个时刻向后台请求数据
        this.$http.get('/api/goods').then((res) => {
          res = res.body;
          if( res.errno === ERR_OK ){
            this.goods = res.data;
            // DOM未更新完成
            this.$nextTick(() => {  //better-scroll的实例初始化要放在vm.$nextTick()里面才生效
              // DOM已经更新完成
              this._initScroll();
            });
          }
        });
}

完整的<script>标签里面的内容如下

<script>
  import BScroll from 'better-scroll'; //引进这个实现上下滑动的插件
  const ERR_OK = 0;
  export default {
      props:{
        seller:{
          type:Object
        }
      },
      data(){
        return {
          goods:[],
        }
      },
      created(){//在这个时刻向后台请求数据
        this.$http.get('/api/goods').then((res) => {
          res = res.body;
          if( res.errno === ERR_OK ){
            this.goods = res.data;
            // DOM未更新完成
            this.$nextTick(() => {  //better-scroll的实例初始化要放在vm.$nextTick()里面才生效
              // DOM已经更新完成
              this._initScroll();
            });
          }
        });
        //定义一个变量
        this.classMap = ['decrease','discount','special','invoice','guarantee'];
      },
      methods:{
        _initScroll(){
          //初始化需要滚动的对象
          this.menuScroll = new BScroll(this.$refs.menuWrapper,{
          });
          this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
          });
        } 
      }
  }
</script>

经过上述的操作,就可以实现在.menu-wrapper和.foods-wrapper内容区域的滚动了


二、实现左边内容和右边内容的联动效果,并且左边选中的.menu-wrapper是呈高亮的状态(接着上面的代码实现),右边.foods-wrapper区域的滚动会对应左边的menu区呈高亮状态。

主要原理是依赖右边.foods-wrapper滚动时的实时y值,也就是y轴落到那个区间,就对应到.menu-wrapper的那个区间。

那么就需要计算.foods-wrapper中每个li的区间的高度(如下图所示)放到一个数组里面,然后去对比实时滚动的y值,从而得到对应的.menu-wrapper中的元素的索引值,然后利用vue的class的绑定,将高亮的效果实现出来。


1、<template>的内容如下

<template>
   <div class="goods">
    <div class="menu-wrapper" ref="menuWrapper">
      <ul>
        <!--currentIndex()在computed里面定义,当计算出来的索引等于index的时候就显示高亮的样式.current-->
        <!--selectMenu(index,$event)实现点击左边的menu,右边滚动到相应的区间,index就是区间索引,$event属性表示原生DOM事件-->
        <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex === index}" @click="selectMenu(index,$event)">
          <span class="text border-1px">
            <span v-show="item.type > 0" class="icon" :class="classMap[item.type]"></span>{{item.name}}
          </span>
        </li>
      </ul>
    </div>
    <div class="foods-wrapper" ref="foodsWrapper">
      <ul>
        <li v-for="item in goods" class="food-list" ref="foodList">
          <h1 class="title">{{item.name}}</h1>
          <ul>
            <li v-for="food in item.foods" class="food-item border-1px">
              <div class="icon">
                <img :src="food.icon" width="57" height="57">
              </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>
</template>

2、<style>标签里面的内容

在例一的css基础上,当当前的的索引等于计算出来的索引的时候,就给.menu-item加上.current样式,如下图所示


完整的css如下

<style lang="stylus">
  @import '../../common/stylus/mixin.styl'
  .goods
    display:flex    /*设置弹性布局*/
    position:absolute /*设定absolute+top+bottom那么该元素就是一个固定高度的容器了*/
    top:174px  /*这个是header+tab组件的高度*/
    width:100%
    overflow:hidden
    bottom:46px /*这个是给底部的购物车留出来的高度*/
    .menu-wrapper
      flex:0 0 80px  /*设置左边的元素的宽度为固定值*/
      width:80px
      background: #f3f5f7
      .menu-item
        display:table
        height:54px
        width:56px
        padding:0 12px
        line-height:14px
        &.current          /*当计算出来的索引等于当前索引,就给相应的menu-item加上.current*/
          position: relative
          margin-top: -1px
          z-index: 10
          background:#fff
          font-weight:700
          .text
            border-none()
        .icon
          display: inline-block
          vertical-align:top
          width:12px
          height:12px
          margin-right :2px
          background-size:12px 12px
          background-repeat :no-repeat
          &.decrease
            bg-image('decrease_3')
          &.discount
            bg-image('discount_3')
          &.guarantee
            bg-image('guarantee_3')
          &.invoice
            bg-image('invoice_3')
          &.special
            bg-image('special_3')
        .text
          display:table-cell
          width:56px
          font-size:12px
          vertical-align: middle
          border-1px(rgba(7,17,27,0.1))
    .foods-wrapper
      flex:1    /*右边的元素的宽度是一个自适应的*/
      .title
        padding-left:14px
        height:26px
        line-height:26px
        border-left: 2px solid #d9dde1
        font-size:12px
        color:rgb(147,153,159)
        background:#f3f5f7
      .food-item
        display:flex
        margin:18px
        padding-bottom:18px
        border-1px(rgba(7,17,27,0.1))
        &:last-child
          border-none()
          margin-bottom:0
        .icon
          flex:0 0 57px
          margin-right:10px
        .content
          flex:1
          .name
            margin:2px 0 8px 0
            height:14px
            line-height:14px
            font-size:14px
            color:rgb(7,17,27)
          .desc,.extra
            line-height:10px
            font-size:10px
            color:rgb(147,153,159)
          .desc
            margin-bottom:8px
            line-height:12px
          .extra
            .count
              margin-right:12px
          .price
            font-weight:700
            line-height:24px
            .now
              margin-right:8px
              font-size:14px
              color:rgb(240,20,20)
            .old
              text-decoration:line-through
              font-size:10px
              color:rgb(147,153,159)
</style>

3、<script>标签里面的内容

1)左边点击menu实现相应的menu呈高亮的状态,并且右边跳到相应的food区,在menu-wrapper下面的li修改成

<li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex === index}" @click="selectMenu(index,$event)">

currentIndex()在computed里面定义,实现v-bind:class

computed:{
        //利用vue中的计算属性computed实时计算,对listHeight遍历,返回当前的左边mune-wrapper的li
        //对应的index,从而让其显示高亮的属性.current
        currentIndex(){
          for( let i = 0;i<this.listHeight.length;i++ ){
            let height1 = this.listHeight[i];
            let height2 = this.listHeight[i+1];
            //当遍历到listHeight最后一个元素的时候,height2的值为undefined,如果是
            //最后一个元素的话!height2为真,后面就不需要判断了
            if( !height2 || (this.scrollY >= height1 && this.scrollY<height2)){
              return i;
            }
          }
          //默认情况下是返回第一个元素
          return 0;
        }
      }
}

selectMenu(index,$event)在methods里面定义,实现点击左边的menu,右边跳到相应的food区

        //点击左边的menu,跳到右边相应的li
        selectMenu(index,event){
          //浏览器的事件对象是没有_constructed属性,当是浏览器触发的时候就return
          //而用better-scroll自定义的事件触发的时候有这个属性,为true
          //用这个属性,就是避免在浏览器点击的时候,触发两次点击的效果
          if( !event._constructed ){
            return;
          }
          //点击左侧的菜单项的时候,右边跳到相应的内容区域
          let foodList = this.$refs.foodList; //获取到右边li对象
          let el = foodList[index];//根据index,获取到右边具体滚动到的li
          this.foodsScroll.scrollToElement(el,300);//要滑动到右边的对象,300完成动作的时间
                                                    
        }
2)右边food区滚动的时候,左边相应的menu区呈高亮的状态

首先,在data()里面定义两个变量:listHeight和scrollY

      data(){
        return {
          goods:[], //用来接收从后台返回的数据
          listHeight:[],  //存放右边每一个li的高度
          scrollY:0       //实时滚动的y轴大小,利用better-scroll的onScroll事件监听这个值
        }
      }
然后,在methods里面定义个函数_calculateHeight(),实现对右边的food的高度的累加
//将右侧的.foods-wrapper里面的每个li的高度进行累加,存放到数组listHeight里面
        _calculateHeight(){
          let foodList = this.$refs.foodList; //获取到所有的ref='foodList'的DOM元素
          let height = 0;
          this.listHeight.push(height); //第一个元素的高度是0
          for( let i =0;i<foodList.length;i++ ){
            let item = foodList[i];
            height += item.clientHeight;//通过原生DOM中的js获取到li的高度,并且累加
            this.listHeight.push(height);
          }
        }

接着,在_initScroll()函数中的对ref='foodsWrapper'的对象添加probeType属性

          this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
            click:true,
            probeType:3 //设置实时监听滚动的位置的效果的属性
          });
同时在_initScroll()函数里面,对this.foodsScroll对象设置滚动监听函数
          //监听右侧滚动区域,左边相应的menu高亮
          //监听滚动事件scroll,实时改变this.scrollY的值,
          // pos是元素所处的位置,内部自动传的
          this.foodsScroll.on('scroll',(pos) => {
            //scrollY是自定义的变量,用于存储滚动的位置
            //Math.round(pos.y)是一个负数
            if( pos.y <= 0 ){
              this.scrollY = Math.abs( Math.round(pos.y) );
            }
          });

然后利用scrollY的值和listHeight里面的 值作比较,实现右边food滚动区域与相应的menu对应,呈现高亮的状态

 computed:{
        //利用vue中的计算属性computed实时计算,对listHeight遍历,返回当前的左边mune-wrapper的li
        //对应的index,从而让其显示高亮的属性.current
        currentIndex(){
          for( let i = 0;i<this.listHeight.length;i++ ){
            let height1 = this.listHeight[i];
            let height2 = this.listHeight[i+1];
            //当遍历到listHeight最后一个元素的时候,height2的值为undefined,如果是
            //最后一个元素的话!height2为真,后面就不需要判断了
            if( !height2 || (this.scrollY >= height1 && this.scrollY<height2)){
              return i;
            }
          }
          //默认情况下是返回第一个元素
          return 0;
        }
      }
}

完整的<script>代码如下

<script>
  import BScroll from 'better-scroll'; //引进这个实现上下滑动的插件
  const ERR_OK = 0;
  export default {
      props:{
        seller:{
          type:Object
        }
      },
      data(){
        return {
          goods:[], //用来接收从后台返回的数据
          listHeight:[],  //存放右边每一个li的高度
          scrollY:0       //实时滚动的y轴大小,利用better-scroll的onScroll事件监听这个值
        }
      },
      computed:{
        //利用vue中的计算属性computed实时计算,对listHeight遍历,返回当前的左边mune-wrapper的li
        //对应的index,从而让其显示高亮的属性.current
        currentIndex(){
          for( let i = 0;i<this.listHeight.length;i++ ){
            let height1 = this.listHeight[i];
            let height2 = this.listHeight[i+1];
            //当遍历到listHeight最后一个元素的时候,height2的值为undefined,如果是
            //最后一个元素的话!height2为真,后面就不需要判断了
            if( !height2 || (this.scrollY >= height1 && this.scrollY<height2)){
              return i;
            }
          }
          //默认情况下是返回第一个元素
          return 0;
        }
      },
      created(){//在这个时刻向后台请求数据
        this.$http.get('/api/goods').then((res) => {
          res = res.body;
          if( res.errno === ERR_OK ){
            this.goods = res.data;
            this.$nextTick(() => {
              //实例化better-scroll
              this._initScroll();
              //计算右边.foods-wrapper的每个li的累加的高度,存放在listHeight中
              this._calculateHeight();
            });
          }
        });
        //定义一个变量
        this.classMap = ['decrease','discount','special','invoice','guarantee'];
      },
      methods:{
        //点击左边的menu,跳到右边相应的li
        selectMenu(index,event){
          //浏览器的事件对象是没有_constructed属性,当是浏览器触发的时候就return
          //而用better-scroll自定义的事件触发的时候有这个属性,为true
          //用这个属性,就是避免在浏览器点击的时候,触发两次点击的效果
          if( !event._constructed ){
            return;
          }
          //点击左侧的菜单项的时候,右边跳到相应的内容区域
          let foodList = this.$refs.foodList; //获取到右边li对象
          let el = foodList[index];//根据index,获取到右边具体滚动到的li
          this.foodsScroll.scrollToElement(el,300);//要滑动到右边的对象,300完成动作的时间
                                                    
        },
        _initScroll(){
          //初始化需要滚动的对象
          this.menuScroll = new BScroll(this.$refs.menuWrapper,{
            click:true
          });
          this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
            click:true,
            probeType:3 //设置实时监听滚动的位置的效果的属性
          });
          //监听右侧滚动区域,左边相应的menu高亮
          //监听滚动事件scroll,实时改变this.scrollY的值,
          // pos是元素所处的位置,内部自动传的
          this.foodsScroll.on('scroll',(pos) => {
            //scrollY是自定义的变量,用于存储滚动的位置
            //Math.round(pos.y)是一个负数
            if( pos.y <= 0 ){
              this.scrollY = Math.abs( Math.round(pos.y) );
            }
          });
        },
        //将右侧的.foods-wrapper里面的每个li的高度进行累加,存放到数组listHeight里面
        _calculateHeight(){
          let foodList = this.$refs.foodList; //获取到所有的ref='foodList'的DOM元素
          let height = 0;
          this.listHeight.push(height); //第一个元素的高度是0
          for( let i =0;i<foodList.length;i++ ){
            let item = foodList[i];
            height += item.clientHeight;//通过原生DOM中的js获取到li的高度,并且累加
            this.listHeight.push(height);
          }
        }
      }
  }
</script>


















  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值