横向滑动+支持点击的scrollbar

897 阅读1分钟

实现思路

首先有一个支持滑动的view,可以基于better-scroll实现 当点击每一个item时,根据点击的item的位置,调用scroll方法将其滚动到视图中间

位置计算主要由以下几种情况 image.png

代码可参考下面完整代码中的 _adjust方法代码注释

进一步丰富支持如美团外卖点餐效果,实现横向bar和竖向滚动联动效果,则只要每次点击或者滑动时更新当前current,是横向和竖向用同一个current,即可

完整代码如下


<template>
  <div
    class="wrap"
    id="wrap">

  <div class="wrapper1" ref="wrapper1" >
    <div class="content1" ref="items"  >
      <div class="item" 
       :class="{'item_active': current === todo}"
      @click="clickHandler(todo)" v-for="(todo) in labels"  :key="todo">{{todo}}</div>
    

    </div>
  </div>


  <cube-scroll-nav-bar :current="current" :labels="labels" @change="changeHandler" >
   <template v-slot:default="slotProps">
    {{ slotProps.txt }}
  </template>
  </cube-scroll-nav-bar>

    <cube-scroll-nav-bar
    direction="vertical"
    :current="current"
    :labels="labels"
    :txts="txts"
    @change="changeHandler">
    <i slot-scope="props">{{props.txt}}</i>
  </cube-scroll-nav-bar>

  </div>
</template>

<script>
import BetterScroll from 'better-scroll'
export default {
  data() {
    return {
      current: '快车',
      labels: [
       
       '快车',
        '小巴',
        '专车',
        '顺风车',
        '代驾',
        '公交',
        '自驾租车',
        '豪华车',
        '二手车',
        '出租车'
      ],
      txts: [
        '1快车',
        '2小巴',
        '3专车',
        '4顺风车',
        '5代驾',
        '6公交',
        '7自驾租车',
        '8豪华车',
        '9二手车',
        '10出租车'
      ],
      bs: {}
    }
  },
  methods: {
    changeHandler(cur) {
      this.current = cur
    },
    clickHandler(cur) {
       console.log(cur)
    this.current = cur

   this._adjust()

    },
    _adjust() {
      this.$nextTick(() => {

       const targetProp =  'clientWidth'
          const current = this.current
          // 滚动容器总宽度为viewportSize 450
          const viewportSize = this.$refs.wrapper1[targetProp]
          const itemsEle = this.$refs.items
          // 滚动条总宽度为scrollerSize 100 * 10
          const scrollerSize = itemsEle[targetProp]
          // 最大可滚动的宽度为minTranslate  450 -  100 * 10
          // 一般scrollerSize 都大于viewportSize 这样才能滚动  所以一般为负数,所以,英文名称用的min)
          // 当一般scrollerSize < 都大于viewportSize表示 不需要滚动,则minTranslate直接为零就好了
          const minTranslate = Math.min(0, viewportSize - scrollerSize)
          // 视图中间距离左侧宽度(滚动容器总宽度的一半)为viewportSize 450/2
          const middleTranslate = viewportSize / 2
          const items = itemsEle.children
          let size = 0
          this.labels.every((label, index) => {
            if (label === current) {
              size += (items[index][targetProp] / 2)
              return false
            }
            // 每一个红色方框宽100
            size += items[index][targetProp]
            return true
          })
           // size为点击的红框的左侧宽度的和+当前红框宽度的一半
           // 当点击的红框的位置在 视图中间左侧的时候 translate < 0 ,反之大于0
          let translate = middleTranslate - size

          console.log('size', size)
          console.log('minTranslate', minTranslate)
          console.log('translate前', translate)

          // Math.min(0, translate) 表示点击位置在视图中间左侧的时候取零,表示不需要异动
          //  Math.max(minTranslate, Math.min(0, translate)),v如果需要滚动,最大滚动距离不会超过minTranslate
          translate = Math.max(minTranslate, Math.min(0, translate))
          console.log('translate最终', translate)
          console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')

          this.bs.scrollTo( translate ,  0 )

      })
    }
  },
  mounted() {
    let bs = new BetterScroll(this.$refs.wrapper1, {
      scrollX:true,
      startX:0
  })
  this.bs =  bs
  console.log(bs)
    setTimeout(()=> {
    // bs.scrollTo(-200,0)

    }, 1000)
  },

}
</script>
<style>
html,
body {
  height: 100%;
}
.wrap {
  height: 600px;
  overflow: scroll;
}
.bg {
  background: #ccc;
  height: 300px;
}
</style>
<style lang="stylus" scoped>
.wrapper1 {
  width: 450px;
  overflow: scroll;
  .content1-wraper {
    overflow: hidden;
    min-height: 0px;
  }
  .content1 {
    white-space: nowrap;
    display: inline-block;
  
  }
    .item {
      box-sizing: border-box;
      display:inline-block;
      width: 102px;
    border: 1px solid red;
    height: 102px;
    }

    .item_active {
      color: blue;
    }


}

div::-webkit-scrollbar { 
    width: 0 !important;
    height: 0 !important; 
}

div{
    -ms-overflow-style: none; 
    overflow: -moz-scrollbars-none;  
}


.layer-container{
  padding: 1.5625rem;
  margin-top: 10px;
  background-color: red;

  .text{
    margin: 0 0 0.9375rem;
    font-family: PingFangSC-Medium;
    font-size: 1rem;
    color: #4B4B4D;
    line-height: 1.3125rem;
  }

  .item-wrapper{
    padding: 1.5625rem 0.9375rem;
    background: rgba(216, 216, 216, 0.25);
    border-radius: 0.4375rem;

    .item{
      margin-top: 1.6875rem;
      display: flex;
      justify-content: space-between;

      &:first-child {
        margin-top: 0;
      }

      .left{
        display: flex;
        font-family: PingFangSC-Medium, sans-serif;
        font-size: 16px;
        color: #323233;
        text-align: justify;
        line-height: 22px;
        .time{
          margin-right: 10px;
        }
      }
      .right{
        position: relative;
        padding-right: 13px;
        font-family: PingFangSC-Medium, sans-serif;
        font-size: 16px;
        line-height: 22px;
        color: #46648C;
        box-sizing: border-box;
        &::after{
          position: absolute;
          content: '';
          display: block;
          right: 0;
          top: 50%;
          width: 7px;
          height: 7px;
          border-top: 2px solid #46648C;
          border-right: 2px solid #46648C;
          transform: translateY(-50%) rotate(45deg);
          line-height: 22px;
        }
      }
    }
  }
}
</style>

<svg
  t="1604497443000"
  class="icon"
  viewBox="0 0 1024 1024"
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  p-id="2837"
  :class="{
    icon: true,
    rotate: popoverShow,
  }"
><path d="M234.88884163 377.47806845l279.65492248 279.65492248 278.8075161-278.8075161z" p-id="2838" data-spm-anchor-id="a313x.7781069.0.i0" class="selected"></path></svg>