Vue + Better – Scroll to achieve mobile singer list alphabetical index navigation. It’s kind of a study note, write a note to make yourself understand a little bit more.

Demo: list-view, use Chrome phone mode to view. After changing to phone mode, if you can’t swipe, just refresh it.

Github: Alpha-indexed navigation on mobile

rendering

Configure the environment

Since vue-CLI and Better -Scroll are used, you need to install vue-CLI first, and then install better -Scroll on NPM.

Better Scroll:

Better Scroll is a plug-in that addresses the needs of mobile (already supported on PC) scrolling scenarios. Its core is the implementation of the reference iscroll, its API design is basically compatible with IScroll, iscroll based on the extension of some features and some performance optimization.

Better scroll is based on native JS and does not rely on any framework. Its compiled code size is 63KB, compressed 35KB, gzip after only 9KB, is a very lightweight JS Lib.

In addition to these two, also use SCSS, Vue-lazyload. SCSS preprocessor, you know what it is, you know what it is. Lazyload implements lazy loading. You can also use lazyload to optimize the experience.

The data directly used the netease Cloud singer list, lazy directly put in the data.

CSS style I do not paste, directly look at the source code can be.

Implementing basic styles

Implement the list of singers directly using v-for and two-side nesting, as well as the index bar on the right.

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>
Copy the code

ShortcutList is obtained by calculating attributes. The first character of the title is required.

shortcutList () {
  return this.singers.map((group) = > {
    return group.title.substr(0.1)})}Copy the code

Use better – scroll

Use better- Scroll to achieve scrolling. Do not forget to use import.

created () {
  // We must wait until the DOM is loaded to initialize the better-Scroll
  setTimeout((a)= > {
    this._initSrcoll()
  }, 20)},methods: {
  _initSrcoll () {
    console.log('didi')
    this.scroll = new BScroll(this.$refs.listView, {
      // Obtain scroll event for listening.
      probeType: 3}}})Copy the code

Using the created method to initialize the Better-Scroll, we use setTimeout because we need to wait until the DOM is loaded. Otherwise, if the Better-Scroll fails to obtain the DOM, the initialization fails.

I’m just going to write the method inside the methods, so it doesn’t look messy, so I can just call it.

When probeType is 3, the Scroll event is dispatched in real time not only during the screen slide, but also during the momentum scroll animation. If the value is not set, the default value is 0, that is, the scroll event is not assigned.

Add click and move events to the index to jump

First, you need to bind a TouchStart event to the index (triggered when a finger is pressed on the screen). Just use v-ON. Then you need to add a data-index to the index to get the value of the index, using :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>
Copy the code

Bind an onShortcutStart method. To achieve the function of clicking the index jump. Then bind an onShortcutMove method to achieve slide jump.

created () {
  // Add a touch to record the properties of the move
  this.touch = {}
  // We must wait until the DOM is loaded to initialize the better-Scroll
  setTimeout((a)= > {
    this._initSrcoll()
  }, 20)},methods: {
  _initSrcoll () {
    this.scroll = new BScroll(this.$refs.listView, {
      probeType: 3.click: true
    })
  },
  onShortcutStart (e) {
    // Get the index of the binding
    let index = e.target.getAttribute('data-index')
    // Use the better- Scroll scrollToElement method to jump
    this.scroll.scrollToElement(this.$refs.listGroup[index])

    // Record the Y coordinate and index when clicked
    let firstTouch = e.touches[0].pageY
    this.touch.y1 = firstTouch
    this.touch.anchorIndex = index
  },
  onShortcutMove (e) {
    // Then record the Y coordinate of the move and calculate how many indexes were moved
    let touchMove = e.touches[0].pageY
    this.touch.y2 = touchMove
    
    // Here 16.7 is the height of the index element
    let delta = Math.floor((this.touch.y2 - this.touch.y1) / 18)

    // Calculate the final position
    // * 1, * 1, * 1, * 1, * 1, * 1, * 1, * 1, * 1, * 1
    let index = this.touch.anchorIndex * 1 + delta
    this.scroll.scrollToElement(this.$refs.listGroup[index])
  }
}
Copy the code

In this way, the index function can be implemented.

Of course that’s not going to satisfy us, is it? We’re going to add some cool special effects. Like index highlighting or something

Move the content index highlight

Emmm, this is where things get a little more complicated. But it takes patience to understand.

We need a better-scroll on method that returns the y-offset as the content is rolled. So we need to add some code to initialize the Better-Scroll. Also, don’t forget to add a scrollY and currentIndex (to record where the highlighted index is) to the data because we need to listen, so add that to the data.

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

  // Listen for the y-offset value
  this.scroll.on('scroll', (pos) => {
    this.scrollY = pos.y
  })
}
Copy the code

Then you need to calculate the height of the content, and add a calculateHeight() method that calculates 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]
// Get this value
Copy the code

Then listen to scrollY in your Watch and look at the code:

watch: {
  scrollY (newVal) {
    // newVal is a negative number when swiping down, so when newVal > 0, currentIndex is directly 0
    if (newVal > 0) {
      this.currentIndex = 0
      return
    }
    
    // Calculate the currentIndex value
    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}}// when super -newVal > last height
    / / because of this. ListHeight head to tail, so need to 2
    this.currentIndex = this.listHeight.length - 2}}Copy the code

Once you get the currentIndex, use it in your HTML.

Class ="{'current': currentIndex === index}"Copy the code

The last thing I want to do is change currentIndex when I slide the index.

Because the code is reusable, and you need to handle boundary cases, you just

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

I rewrote the function to reduce the amount of code.

// When scrollToElement is used, change scrollY to calculate currentIndex because of the watch
scrollToElement (index) {
  // Handle the boundary case
  // Because index is calculated by sliding distance
  // So if you slide up above the index box, it will be < 0, and if you slide up, it will be above the maximum value
  if (index < 0) {
    return
  } else if (index > this.listHeight.length - 2) {
    index = this.listHeight.length - 2
  }
  // listHeight is positive, so add -
  this.scrollY = -this.listHeight[index]
  this.scroll.scrollToElement(this.$refs.listGroup[index])
}
Copy the code

lazyload

Lazyload plugin by the way, to add to the user experience.

Method of use

  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')})Copy the code

Add a loading image and use WebPack’s require to get the loading image.

  1. And then when you need to use it, put:src=""Switch tov-lazy=""To achieve the lazy image loading function.

conclusion

With alphabetic navigation on mobile, it’s pretty hard (for me).

Better Scroll’s on is used to get the move offset (for highlighting) and the scrollToElement to jump to the corresponding position (for jumping). And use the Touch event to listen for the touch to get the starting position, as well as the slide distance (to calculate the final position).

harvest

  • Have an understanding of the Touch event
  • I got a little more comfortable with the better scroll
  • Vue is a little more proficient, Emmm
  • You’ll get experience writing things like this in the future

This article originally appeared on the Blog of Jin. Please follow me on GitHub to get started and learn about the front end.