Vue + better-scroll implements alphabetical index navigation on mobile

vue + better-scroll implements alphabetical index navigation of mobile singer list. It can be regarded as a study note. Write a note to let yourself understand a little more deeply.

Demo: list-view , use chrome phone mode to view. After changing to mobile phone mode, if you can't swipe, refresh it and it will be OK.

Github: Mobile alphabetical index navigation

renderings

Configuration Environment

Because vue-cli and better-scroll are used, first install vue-cli, and then npm install better-scroll .

A brief introduction to better-scroll:

better-scroll is a plug-in that focuses on solving the needs of various scrolling scenarios on the mobile terminal (PC is already supported). Its core is the implementation of iscroll for reference. Its API design is basically compatible with iscroll. On the basis of iscroll, some features have been extended and some performance optimizations have been made.

better-scroll is implemented based on native JS and does not depend on any framework. Its compiled code size is 63kb, compressed is 35kb, and gzip is only 9kb, it is a very lightweight JS lib.

In addition to these two, scss and vue-lazyload are also used. The scss preprocessor, which everyone knows, is the same with others. lazyload implements lazy loading, you can do it without it, mainly to optimize the experience.

The data directly uses the singer list of NetEase Cloud, and if you are lazy, you put it directly in the data.

I won't post CSS styles, just look at the source code.

Implement basic styles

Direct use of v-for and double-sided nesting to implement the list of singers, as well as the right index bar.

HTML structure:

<ul>
  <li v-for="group in singers" 
  class="list-group" 
  :key="group.id" 
  ref="listGroup">
    <h2 class="list-group-title">{{ group.title }}</h2>
    <ul>
      <li v-for="item in group.items" 
      class="list-group-item" :key="item.id">
        <img v-lazy="item.avatar" class="avatar">
        <span class="name">{{ item.name }}</span>
      </li>
    </ul>
  </li>
</ul>
<div class="list-shortcut">
  <ul>
    <li v-for="(item, index) in shortcutList"
    class="item"
    :data-index="index"
    :key="item.id"
    >
      {{ item }}
    </li>
  </ul>
</div>

The shortcutList is obtained by calculating the property, just take the first character of the title.

shortcutList () {
  return this.singers.map((group) => {
    return group.title.substr(0, 1)
  })
}

Use better-scroll

Scrolling is implemented using better-scroll. By the way, don't forget to use import when you use it.

created () {
  // 初始化 better-scroll 必须要等 dom 加载完毕
  setTimeout(() => {
    this._initSrcoll()
  }, 20)
},
methods: {
  _initSrcoll () {
    console.log('didi')
    this.scroll = new BScroll(this.$refs.listView, {
      // 获取 scroll 事件,用来监听。
      probeType: 3
    })
  }
}

Use the created method for better-scroll initialization, and use setTimeout because you need to wait until the DOM is loaded. Otherwise, better-scroll will fail to initialize if it can't get the dom.

Here, the method is written in two methods, so that it will not look messy, just call it directly.

When initializing, pass in two probeType: 3, and explain: when the probeType is 3, not only in the process of screen sliding, but also in the running process of momentum scrolling animation, the scroll event is dispatched in real time. If this value is not set, its default value is 0, that is, no scroll event is dispatched.

Add click events and move events to the index to jump

First, you need to bind a touchstart event to the index (triggered when the finger is pressed on the screen), just use v-on directly. Then you need to add a data-index to the index so that you can get the value of the index and use it :data-index="index".

<div class="list-shortcut">
  <ul>
    <li v-for="(item, index) in shortcutList"
    class="item"
    :data-index="index"
    :key="item.id"
    @touchstart="onShortcutStart"
    @touchmove.stop.prevent="onShortcutMove"
    >
      {{ item }}
    </li>
  </ul>
</div>

Bind an onShortcutStart method. Realize the function of click index jump. Then bind an onShortcutMove method to realize sliding jump.

created () {
  // 添加一个 touch 用于记录移动的属性
  this.touch = {}
  // 初始化 better-scroll 必须要等 dom 加载完毕
  setTimeout(() => {
    this._initSrcoll()
  }, 20)
},
methods: {
  _initSrcoll () {
    this.scroll = new BScroll(this.$refs.listView, {
      probeType: 3,
      click: true
    })
  },
  onShortcutStart (e) {
    // 获取到绑定的 index
    let index = e.target.getAttribute('data-index')
    // 使用 better-scroll 的 scrollToElement 方法实现跳转
    this.scroll.scrollToElement(this.$refs.listGroup[index])

    // 记录一下点击时候的 Y坐标 和 index
    let firstTouch = e.touches[0].pageY
    this.touch.y1 = firstTouch
    this.touch.anchorIndex = index
  },
  onShortcutMove (e) {
    // 再记录一下移动时候的 Y坐标,然后计算出移动了几个索引
    let touchMove = e.touches[0].pageY
    this.touch.y2 = touchMove
    
    // 这里的 16.7 是索引元素的高度
    let delta = Math.floor((this.touch.y2 - this.touch.y1) / 18)

    // 计算最后的位置
    // * 1 是因为 this.touch.anchorIndex 是字符串,用 * 1 偷懒的转化一下
    let index = this.touch.anchorIndex * 1 + delta
    this.scroll.scrollToElement(this.$refs.listGroup[index])
  }
}

In this way, the function of index can be realized.

Of course, this will not satisfy us, right? We have to add cool special effects. Such as index highlighting or something~~

Mobile Content Index Highlighting

emmm, it's a bit complicated at this time. But you can understand it with patience.

We need the on method of better-scroll, which returns the Y-axis offset value when the content is scrolled. So you need to add some code when initializing better-scroll. By the way, don't forget to add a scrollY in data, and currentIndex (used to record the position of the highlighted index) because we need to listen, so add it in data.

_initSrcoll () {
  this.scroll = new BScroll(this.$refs.listView, {
    probeType: 3,
    click: true
  })

  // 监听Y轴偏移的值
  this.scroll.on('scroll', (pos) => {
    this.scrollY = pos.y
  })
}

Then you need to calculate the height of the content and add a calculateHeight() method to calculate the height of the indexed content.

_calculateHeight () {
  this.listHeight = []
  const list = this.$refs.listGroup
  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)
  }
}

// [0, 760, 1380, 1720, 2340, 2680, 2880, 3220, 3420, 3620, 3960, 4090, 4920, 5190, 5320, 5590, 5790, 5990, 6470, 7090, 7500, 7910, 8110, 8870]
// 得到这样的值

Then listen to scrollY in watch and see the code:

watch: {
  scrollY (newVal) {
    // 向下滑动的时候 newVal 是一个负数,所以当 newVal > 0 时,currentIndex 直接为 0
    if (newVal > 0) {
      this.currentIndex = 0
      return
    }
    
    // 计算 currentIndex 的值
    for (let i = 0; i < this.listHeight.length - 1; i++) {
      let height1 = this.listHeight[i]
      let height2 = this.listHeight[i + 1]

      if (-newVal >= height1 && -newVal < height2) {
        this.currentIndex = i
        return
      }
    }
    
    // 当超 -newVal > 最后一个高度的时候
    // 因为 this.listHeight 有头尾,所以需要 - 2
    this.currentIndex = this.listHeight.length - 2
  }
}

After getting the currentIndex, use it in html.

给索引绑定 class -->  :class="{'current': currentIndex === index}"

Finally, change the currentIndex when processing the sliding index.

Because code can be reused and edge cases need to be handled,

this.scroll.scrollToElement(this.$refs.listGroup[index])

Rewrite a function to reduce the amount of code.

// 在 scrollToElement 的时候,改变 scrollY,因为有 watch 所以就会计算出 currentIndex
scrollToElement (index) {
  // 处理边界情况
  // 因为 index 通过滑动距离计算出来的
  // 所以向上滑超过索引框框的时候就会 < 0,向上就会超过最大值
  if (index < 0) {
    return
  } else if (index > this.listHeight.length - 2) {
    index = this.listHeight.length - 2
  }
  // listHeight 是正的, 所以加个 -
  this.scrollY = -this.listHeight[index]
  this.scroll.scrollToElement(this.$refs.listGroup[index])
}

lazyload

The lazyload plugin is also by the way, to increase the user experience.

Instructions

  1. npm install first
  2. import in main.js, then Vue.use
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  loading: require('./common/image/default.jpg')
})

Add a loading image and use webpack's require to get the image.

  1. Then when you need to use it, :src=""replace v-lazy=""it with to realize the function of image lazy loading.

Summarize

The alphabetical index navigation on the mobile terminal is implemented like this, and it still feels very difficult (for me).

The main thing is to use the on of better-scroll to get the movement offset value (to achieve highlighting), and scrollToElement to jump to the corresponding position (to achieve jumping). And use the touch event to listen for touches to get the starting position, and the sliding distance (calculate the final position).

reward

  • Learn about touch events
  • A little more proficient in the use of better-scroll
  • Vue is also proficient, emmm
  • I'll have the experience to write something like this in the future.

Author: CaiJinyc
Link: https://juejin.im/post/5aeaea926fb9a07aac244d41
Source: Nuggets The
copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325867396&siteId=291194637