Author: Fu Nan, Didi WebApp Architecture Team

BetterScroll is an open source plug-in (GitHub address) that focuses on various mobile scrolling scenarios. It is suitable for scrolling lists, selectors, rotatable charts, index lists, and open-screen boot scenarios.

In order to meet these scenarios, it not only supports the flexible configuration of inertial scrolling, bounding rebound, scrollbar fade in and out effects, so that the scrolling is more smooth, but also provides a lot of API methods and events, so that we can quickly achieve the requirements of the scrolling scene, such as pull refresh, pull up load.

Because it is implemented in native JavaScript and does not rely on any framework, it can be referenced in native JavaScript or used in conjunction with the current front-end MVVM framework. For example, the examples on its website are in conjunction with Vue.

First, let’s take a look at how it makes scrolling smoother.

Make scrolling smoother

On the mobile side, if you have used Overflow: Scroll to generate a scroll container, you will find that the scroll is rather sluggish. Why does this happen?

This is because we are used to the scrolling experience of current mainstream operating systems and browsers. For example, when you scroll to the edge, you bounce back, when you stop sliding, you continue to scroll for a while, and when you swipe quickly, the page will also scroll quickly. This native rolling container doesn’t have one, and it feels stuck.

BetterScroll scroll experience

Give BetterScroll a try. Experience the address

It can be found that after increasing the effect of inertia rolling and edge rebound, it is obviously smooth and comfortable. So how do these effects work?

Inertial scrolling

BetterScroll will continue to scroll for a while after the user finishes sliding. First look at the bscroll.prototype. _end function in the source code. This is the function that handles the touchEnd, mouseup, TouchCancel, and mousecancel events.

BScroll.prototype._end = function (e) {... if (this.options.momentum && duration < this.options.momentumLimitTime && (absDistY > this.options.momentumLimitDistance || absDistX > this.options.momentumLimitDistance)) {
      let momentumX = this.hasHorizontalScroll ? momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0.this.options)
        : {destination: newX, duration: 0}
      let momentumY = this.hasVerticalScroll ? momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0.this.options)
        : {destination: newY, duration: 0}
      newX = momentumX.destination
      newY = momentumY.destination
      time = Math.max(momentumX.duration, momentumY.duration)
      this.isInTransition = 1}... }Copy the code

The purpose of the above code is to calculate the distance and time of inertial scrolling using momentum function at the end of user sliding operation if inertial scrolling needs to be enabled. Activity, which calculates the scrolling distance based on the speed of the user’s sliding action and deceleration, is also configurable.

function momentum(current, start, time, lowerMargin, wrapperSize, options) {... let distance = current - startlet speed = Math.abs(distance) / time
  ...
  let duration = swipeTime
  let destination = current + speed / deceleration * (distance < 0 ? - 1 : 1)... }Copy the code

Edge of springback

There are two processing steps for the rebound after the edge. The first step is to slow down after the edge, and the second step is to rebound to the edge. The first step is the bscroll.prototype. _move function in the source code, which is the touchMove and mousemove event handler, that is, the logic in the user’s sliding operation.

// Slow down or stop if outside of the boundaries
if (newY > 0 || newY < this.maxScrollY) {
    if (this.options.bounce) {
        newY = this.y + deltaY / 3
    } else {
        newY = newY > 0 ? 0 : this.maxScrollY
    }
}Copy the code

The second step is to call BScroll. Prototype. ResetPosition function, rebound to the boundary.

BScroll.prototype.resetPosition = function (time = 0, easeing = ease.bounce) {... let y =this.y
    if (!this.hasVerticalScroll || y > 0) {
      y = 0
    } else if (y < this.maxScrollY) {
      y = this.maxScrollY
    }
    ...
    this.scrollTo(x, y, time, easeing)
    ...
  }Copy the code

Smooth scrolling is just the basics. BetterScoll’s real power is to provide a large number of common/custom options, API methods and events to make scrolling requirements more efficient.

How to apply to various requirements scenarios

BetterScroll is used in various scenarios using Vue as an example.

Plain scrolling list

For example, here’s a list:

<div ref="wrapper" class="list-wrapper">
  <ul class="list-content">
    <li @click="clickItem($event,item)" class="list-item" v-for="item in data">{{item}}</li>
  </ul>
</div>Copy the code

We want it to scroll vertically, just by doing a simple initialization of the container.

import BScroll from 'better-scroll'

const options = {
  scrollY: true // Since scrollY defaults to true, it can be omitted
}

this.scroll = new BScroll(this.$refs.wrapper, options)Copy the code

When using BetterScroll in Vue, it is important to note that the DOM element of the list is not generated in the Vue template before the list rendering is complete, so you need to ensure that the list rendering is complete before creating BScroll instances. The best time to initialize BScroll is mouted’s nextTick.

// In Vue, BScroll is initialized when the list is rendered
mounted() {
   setTimeout((a)= > {
     this.scroll = new BScroll(this.$refs.wrapper, options)
   }, 20)},Copy the code

Once initialized, the Wrapper container should scroll gracefully and use the API methods and events provided by the BScroll instance this.scroll.

Here are a few common options, methods, and events.

The scroll bar

The scrollbar option, which configures the scrollbar, defaults to false. When set to True or an Object, turn on the scrollbar. You can also use the fade property to configure whether the scroll bar fades in and out with the scroll operation or stays on.

// Fade defaults to true, the scrollbar fades in and out
options.scrollbar = true

// The scroll bar is always displayed
options.scrollbar = {
  fade: false
}

this.scroll = new BScroll(this.$refs.wrapper, options)Copy the code

Concrete effects can be seen in plain scrolling list – example.

The drop-down refresh

PullDownRefresh option to configure the pullDownRefresh function. When set to true or an Object, pull down refresh is enabled. You can set the top pull down distance (threshold) to determine the refresh timing and the bounce stop distance (stop).

options.pullDownRefresh = {
  threshold: 50.// Trigger the pullingDown event when the pull-down exceeds the top 50px
  stop: 20 // In the process of refreshing the data, the rebound stays 20px away from the top
}

this.scroll = new BScroll(this.$refs.wrapper, options)Copy the code

Listen for pullingDown events to refresh data. And after the refresh is complete, the finishPullDown() method is called to bounce back to the top boundary

this.scroll.on('pullingDown', () = > {// In the process of refreshing the data, the rebound stays 20px away from the top
  RefreshData()
    .then((newData) = > {
      this.data = newData
      // After the refresh is complete, call the finishPullDown method and bounce back to the top
      this.scroll.finishPullDown()
  })
})Copy the code

Concrete effects can be seen in plain scrolling list – example.

Pull on loading

PullUpLoad option to configure the pull-up loading function. When set to true or an Object, pull-up loading can be enabled, and the threshold can be set to determine when to start loading

options.pullUpLoad = {
  threshold: - 20 // Trigger the pullingUp event when the pull-up exceeds the bottom 20px
}

this.scroll = new BScroll(this.$refs.wrapper, options)Copy the code

Listen for the pullingUp event to load new data.

this.scroll.on('pullingUp', () => {
  loadData()
    .then((newData) = > {
      this.data.push(newData)
  })
})Copy the code

Concrete effects can be seen in plain scrolling list – example.

The selector

The wheel option is used to enable and configure the selector. AdjustTime can be configured to adjust the current index (selectedIndex), the curvature of the list (Rotate), and adjustTime for switching options.

options.wheel = {
  selectedIndex: 0.rotate: 25.adjustTime: 400
}

// Initializes each column of the selector
this.wheels[i] = new BScroll(wheelWrapper.children[i], options)Copy the code

Specific effects visible in selectors – example.

The linkage selector needs to listen to the selection of each selection list to change the other selection list.

data() {
   return {
     tempIndex: [0.0.0]}},...// Listen for selections from each selection list
this.wheels[i].on('scrollEnd', () = > {this.tempIndex.splice(i, 1.this.wheels[i].getSelectedIndex())
})
...
// Determine the contents of the other selection lists based on the current selection
computed: {
  linkageData() {
    const provinces = provinceList
    const cities = cityList[provinces[this.tempIndex[0]].value]
    const areas = areaList[cities[this.tempIndex[1]].value]

    return [provinces, cities, areas]
  }
},Copy the code

The effect is visible in the selector – the linkage selector in the example.

Shuffling figure

The SNAP option is used to enable and configure the multicast graph. You can configure whether to play the rotation graph in a loop (loop), the width and height of each page (stepX), the switching threshold (threshold), and the switching speed (speed).

options = {
  scrollX: true.snap: {
    loop: true.// Start looping
    stepX: 200.// Each page should be 200px wide
    stepY: 100.// Each page should be 100px high
    threshold: 0.3.// Switch images when the scroll distance exceeds 30% of the width/height
    speed: 400 // Switch animation length 400ms}}this.slide = BScroll(this.$refs.slide, options)Copy the code

Specific effects can be seen in the wheel – example diagram.

Special scenario

In addition to basic scrolling scenarios such as regular scroll lists, selectors, and rotatable images, You can use BetterScroll’s capabilities to create special scenarios.

The index lists

Index list, first need to scroll in real time to listen to which index area, to update the current index. In this scenario, we can use the probeType option, which, when set to 3, will issue Scroll events in real time throughout the scroll process. To get the position in the scrolling process.

options.probeType = 3
this.scroll = new BScroll(this.$refs.wrapper, options)

this.scroll.on('scroll', (pos) => {
  const y = pos.y

  for (let i = 0; i < listHeight.length - 1; i++) {
    let height1 = listHeight[i]
    let height2 = listHeight[i + 1]
    if (-y >= height1 && -y < height2) {
      this.currentIndex = i
    }
  }
})Copy the code

When an index is clicked, use the scrollToElement() method to scroll to the index region.

scrollTo(index) {
  this.$refs.indexList.scrollToElement(this.$refs.listGroup[index], 0)}Copy the code

Concrete effect visible index list – example.

Opening the guide

Open screen boot, in fact, is not automatically played in the horizontal scrolling wheel broadcast map.

options = {
  scrollX: true.snap: {
    loop: false}}this.slide = BScroll(this.$refs.slide, options)Copy the code

Concrete effect visible open screen boot – example. Since this requirement scenario is generally only available on mobile, it is best to see the effect in mobile mode.

Free rolling

FreeScroll option, used to enable free scrolling, allowing horizontal and vertical scrolling at the same time, not limited to one direction.

options.freeScroll = trueCopy the code

Also note that this option does not work if eventPassthrough is set to keep native scrolling.

Concrete effects visible in free scrolling – example.

summary

BetterScroll can be used in almost any scrolling scenario, but this article only describes the positions used in some typical scenarios.

BetterScroll is a plugin designed to address the need for scrolling on mobile devices. The many options, methods, and events that BetterScroll opens up give us the ability to scroll faster, more flexibly, and at precisely the right time.


Welcome to didi FE blog: github.com/DDFE/DDFE-b…