better-scroll左右联动

音乐人列表滚动与索引同步
本文介绍了一个使用Vue.js和Better Scroll实现的音乐人列表滚动与右侧字母索引同步的技术方案。通过监听滚动事件,计算左侧列表高度,使右侧字母索引随滚动位置自动高亮显示当前字母,同时提供了触摸右侧字母快速定位到左侧相应位置的功能。

<template>
  <div class="box">
    <div class="tab"></div>
    <div class="content-box">
      <div ref="wrapBox" class="wrapBox">
        <div class="scrollBox">
          <ul v-for="(item, i) in list" :key="i" ref="listItem">
            <h2 class="letter">{{item.letter}}</h2>
            <ul v-for="(iitem, ii) in item.letterList" :key="ii">
              <li class="name">{{iitem}}</li>
            </ul>
          </ul>
        </div>
      </div>
      <!-- 右侧导航栏 -->
      <div class="rightBar" @touchstart='onShortcutTouchstart' @touchmove.stop.prevent='onShortcuutTouchMove'>
        <span ref="letterList" :class="[currentIndex === i ?'active':'']" v-for="(item, i) in list" :key="i" :data-index='i'>{{item.letter}}</span>
      </div>
    </div>
  </div>
</template>
<style scoped>
  .box{
    background-color: #222;
  }
  .tab{
    height: 44px;
  }
  .letter{
    background-color: #ddd;
  }
  .wrapBox{
    position: fixed;
    top: 44px;
    bottom: 0;
    width: 100%;
    overflow: hidden;
  }
  .content-box{
    position: fixed;
    width: 100%;
    bottom: 0;
    overflow: hidden;
    top: 0;
    top: 44px;
  }
  .name{
    line-height: 40px;
  }
  .rightBar{
    position: absolute;
    display: flex;
    flex-direction: column;
    top: 50%;
    right: 0;
    text-align: center;
    padding: 0 20px;
    transform: translateY(-50%)
  }
  .active{
    color: red;
  }
</style>
<script>
import BScroll from 'better-scroll'
export default {
  data () {
    return {
      scrollY: -1, 
      currentIndex: 0,
      list: [
        {
          letter: '热门',
          letterList: ['薛之谦','薛之谦','薛之谦','薛之谦','薛之谦','薛之谦','薛之谦','薛之谦']
        },{
          letter: 'A',
          letterList: ['A-Lin','A-Lin','A-Lin','A-Lin','A-Lin','A-Lin','A-Lin']
        },{
          letter: 'B',
          letterList: ['本兮','本兮','本兮','本兮','本兮','本兮','本兮','本兮']
        },{
          letter: 'C',
          letterList: ['陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅','陈奕迅']
        },{
          letter: 'H',
          letterList: ['华晨宇','华晨宇','华晨宇','华晨宇','华晨宇']
        },{
          letter: 'L',
          letterList: ['林俊杰','林俊杰','林俊杰','林俊杰','林俊杰']
        },{
          letter: 'M',
          letterList: ['马伊利','马伊利','马伊利','马伊利','马伊利','马伊利','马伊利','马伊利']
        }
      ]
    }
  },
  watch: {
    list() {
      setTimeout(() => {
        this.calculateHeight()
      }, 20);
    },
    scrollY (newY) {
      const listHeight = this.listHeight
      // 当滚动到顶部,newY > 0
      if(newY > 0) {
        this.currentIndex = 0
        return
      }
      // 当在中间滚动
      for(let i=0; i<listHeight.length - 1; i++) {
        let height1 = listHeight[i]
        let height2 = listHeight[i+1]
        if(-newY >= height1 && -newY < height2) {
          this.currentIndex = i
          return
        }
      }
      // 当滚动到底部,且 -newY 大于最后一个元素的上限
      this.currentIndex = listHeight.length - 2
    }
    // 
  },
  created () {
    this.touch = {}
    this.listenScroll = true
  },
  mounted () {
    this._initScroll()
    // 这个方法可以放在watch中,左边滚动部分封装成一个组件,然后传入渲染的数据,监听这个渲染的数据,这里因为没有做组件,所以手动调用一下
    this.calculateHeight()  
  },
  methods: {
    _initScroll () {
      this.scroll = new BScroll(this.$refs.wrapBox, {
        scrollY: true,
        probeType: 3
      })
      if(this.listenScroll) {
        this.scroll.on('scroll', pos => {
          this.scrollY = pos.y
        })
      }
    },
    // 点击右侧的字母
    onShortcutTouchstart (e) {
      let anchorIndex = e.target.getAttribute('data-index')  //获取右侧索引
      // 点击右侧的字母栏,左边跳转到对应的字母
      // this.$refs.wrapBox.scrollToElement(this.$refs.listItem[anchorIndex], 0)
      this.scrollToElement(this.$refs.listItem[anchorIndex], 0)
      // 点击高亮
      this.scrollY = -this.listHeight[anchorIndex]
      // 滚动右侧字母
      let firstTouch = e.touches[0]
      this.touch.y1 = firstTouch.pageY
      this.touch.anchorIndex = anchorIndex
    },
    // 滚动右侧的字母
    onShortcuutTouchMove (e) {
      let ANCHOR_HEIGHT = 21  //右侧每个字母的高度
      let firstTouch = e.touches[0]
      this.touch.y2 = firstTouch.pageY
      let delta = Math.floor((this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT)
      let anchorIndex = parseInt(this.touch.anchorIndex) + delta
      this.scrollToElement(this.$refs.listItem[anchorIndex], 0)
      // 实现点击高亮
      // 这里的anchorIndex可能超过listHeight的范围而导致 this.listHeight[anchorIndex]  NaN 所以这里要对 anchorIndex 添加一层校验
      if(!this.listHeight[anchorIndex] && anchorIndex !== 0) {
        return
      }
      this.scrollY = -this.listHeight[anchorIndex]
    },
    // 计算左边每一块字母的高度,使得左边的滚动右边对应字母高亮
    calculateHeight () {
      this.listHeight = []
      const list = this.$refs.listItem
      let height = 0
      this.listHeight.push(height)
      for(let i=0; i<list.length; i++) {
        let item = list[i]
        height += item.clientHeight
        this.listHeight.push(height)
      }
    },
    scrollTo () {
      this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
    },
    scrollToElement () {
      this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
    }
  }
}
</script>

 

### 使用 BetterScroll 实现左右联动菜单 #### 工作原理 BetterScroll 可以实现复杂的滚动交互效果,包括左右两侧菜单的联动。这种设计常见于电商应用的商品分类页面,在左侧展示类别列表,当用户点击某个类目时,右侧会自动滚动至对应的详情区域;反之亦然。 #### 准备工作 项目需先集成 `better-scroll` 库[^1]: ```bash yarn add better-scroll ``` 对于样式管理工具的选择取决于个人喜好或团队标准,这里推荐使用 Stylus 来编写 CSS 预处理器代码[^3]: ```bash yarn add stylus-loader@3.0.2 --save-dev ``` #### HTML 结构搭建 创建两个容器分别承载左栏导航项以及右栏具体内容区。注意保持结构清晰简洁以便后续操作。 ```html <div class="wrapper"> <!-- 左边固定宽度 --> <div id="menu-wrapper" style="height: 100%;"> <ul> <li v-for="(item, index) in menuItems" :key="index">{{ item.name }}</li> </ul> </div> <!-- 右边自适应剩余空间 --> <div id="content-wrapper" style="flex-grow: 1; height: 100%;"> <section v-for="(section, idx) in sections" :id="'sec-' + idx" :key="idx"> {{ section.title }} </section> </div> </div> ``` #### 初始化 BetterScroll 实例并配置参数 在 Vue 组件内部初始化 BetterScroll 对象,并监听相应事件来同步两边的状态变化。 ```javascript import BScroll from 'better-scroll'; export default { data() { return { scrollY: 0, listHeight: [], probeType: 3, listenScroll: true, menuItems: [...], // 菜单项数组 sections: [...] // 各个板块的数据源 }; }, mounted() { this._initScroller(); this._calculateHeights(); }, methods: { _initScroller () { const that = this; /* 初始化左边 */ new BScroll(document.getElementById('menu-wrapper'), {}); /* 初始化右边 */ let contentWrapper = document.getElementById('content-wrapper'); this.contentBS = new BScroll(contentWrapper, { click: true }); // 添加滚动监听器 this.contentBS.on('scroll', (pos) => { that.scrollY = Math.abs(Math.round(pos.y)); }); }, _calculateHeights () { let heights = []; let totalHeight = 0; Array.from(this.$el.querySelectorAll('#content-wrapper>section')).forEach((element) => { heights.push(totalHeight); totalHeight += element.clientHeight; }); heights.push(totalHeight); this.listHeight = heights; } } } ``` #### 处理上下文关联逻辑 为了让两部分能够相互影响,还需要额外处理一些细节问题,例如计算各个区块的高度用于判断当前可视范围内的索引值,进而更新左侧高亮状态等。 ```javascript computed: { currentIndex() { for(let i=0;i<this.listHeight.length-1;i++){ let topHeight=this.listHeight[i]; let bottomHeight=this.listHeight[i+1]; if(!this.scrollY && i===0){ return 0; }else if(this.scrollY>=topHeight&&this.scrollY<=bottomHeight){ return i; } } return this.listHeight.length - 2; } }, watch:{ currentIndex(newIndex){ let menuItem=document.querySelector(`#menu-wrapper li:nth-child(${newIndex+1})`); if(menuItem){ let bsMenu=new BScroll("#menu-wrapper",{}); bsMenu.scrollToElement(menuItem,300); } } } ``` 以上即为利用 BetterScroll 构建响应式双列布局的基础流程概述][^[^23]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值