What is better – scroll

BetterScroll is a plug-in that focuses on various scrolling scenarios on mobile devices (supported on PCS). Its core is the implementation of iscroll reference, its API design is basically compatible with IScroll, on the basis of IScroll and extended some features and do some performance optimization.

Simple use of Better Scroll in vue

<div class="wrapper"> <ul class="content"> <li>... </li> <li ref='child'>... </li> ... </ul> <! -- You can put some other DOM here, but it won't affect scrolling --> </div>Copy the code

To use better-Scroll, you must first have three layers of HTML elements, with wrappers at the top, fixed height, and overflow: Hidden style. Why do you need a middle layer of content instead of just containing all

  • tags in wrapper? Since better-Scroll handles scrolling for the first child by default and ignores other children, we need the Content layer to wrap the actual scrolling element. NPM install better Scroll and import
  • import Bscroll from 'better-scroll'
    export default {
    	mounted () {
          this.scroll = new Bscroll(this.$refs.wrapper, {
            click: true
          })
        }
    }
    Copy the code

    The wrapper dom is initialized by this.$ref. The second constructor argument is the configuration item. Since we use the better-Scroll element to be unclickable by default, we can dispatch click events by setting click to true. Better Scroll also provides a scrollToElement method to scroll the page to a specified child element:

    this.scrollToElement(this.$ref.child)
    Copy the code

    Summary on pit

    After initializing Bscroll, the page cannot scroll

    First of all, we need to know the principle of better Scroll, as shown in the following figure, only when the content height is greater than the wrapper height can the page scroll.

    However, during the development of the project, I found that when my content height was actually higher than the wrapper height, the page still couldn’t scroll. Why? In fact, the reason may be that the timing of the new Scroll is wrong, so that the wrapper height is larger than the content during initialization. The height of the content is larger than the wrapper, which was changed after the bScroll initialization due to data acquisition or some other way. So when we initialize Bscroll is important.

    In many practical applications, the data in our lists is often retrieved dynamically. It is an asynchronous process to change data to trigger page re-rendering. We need to initialize Bscroll after obtaining data and re-rendering the page.

    Vue updates the DOM asynchronously. As long as it listens for data changes, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is triggered more than once, it will only be pushed into the queue once. This removal of duplicate data while buffering is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, “TICK,” Vue refreshes the queue and performs the actual (de-duplicated) work. Vue internally attempts to use native Promise.then, MutationObserver, and setImmediate for asynchronous queues, and setTimeout(fn, 0) instead if the execution environment does not support it.

    Therefore, we can use the nextTick function immediately after the data changes, and then new Bsroll in the next event loop, after the DOM update.

    created () {
    	requestData().then((res) => {
        	this.data = res.data
            this.$nextTick(() => {
            	this.scroll = new Bscroll(this.$refs.wrapper)
            })
        })
    }
    Copy the code

    scroll.refresh()

    Since it is not possible to initialize a Bscroll every time data changes, we can use refresh(), another method provided by Better-Scroll, to trigger recalculation of the wrapper and content heights. Such as in the updated hook function or a function that listens for certain data.

    <template> <div>... <city-list :cities="cities"></city-list> ... </div> </template> export default { name: 'City', data () { return { cities: {} } } }, methods: { getCityInfo () { axios.get('/api/city.json').then(this.getCityInfoSucc) }, getCityInfoSucc (res) { res = res.data if (res.ret && res.data) { const data = res.data this.cities = data.cities this.hotCities = data.hotCities } }, }, created () { this.getCityInfo() } }Copy the code
    <div class="list" ref="wrapper"> <div>... <div class="area" v-for="(item, key) of cities" :key="key" :ref="key"> <div class="title border-topbottom">{{key}}</div> <div class="item-list"> <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id" @click="handleCityClick(innerItem.name)" > {{innerItem.name}} </div> </div> </div> </div> </div> export default { name: 'CityList', props: { cities: Object }, methods: { mounted () { this.scroll = new Bscroll(this.$refs.wrapper, { click: true }) }, updated () { this.scroll.refresh() } }Copy the code

    The above is part of the code in my development project. In this code, the parent component requests data through AXIOS after creation and passes the value to the child component in the form of props. The child component needs to render the list according to this value, that is, the child component uses Bscroll, but the data and DOM structure are dynamically updated depending on the value passed by the parent component. The updated hook function updates the props when the parent component succeeds in getting the data. Then scroll recalculates the height of the DOM element. The list should scroll normally. Of course, if you worry about frequent changes of data, which may lead to frequent refresh and affect performance, you can use timer to perform anti-shake operation.

    Update () {const refresh=this.debounce(500) refresh()}, methods: { debounce(delay){ let timer1 = null return () => { if (timer1) clearTimeout(timer1) timer1 = setTimeout(() => { this.scroll.refresh() }, delay) } }Copy the code

    Better – scroll and scrollBehavior

    In my project, I wanted the list to return to the top every time I went to the page, so I used vue-Router’s scrollBehavior:

    scrollBehavior (to, from, savedPosition) {
      if (savedPosition) {
        return savedPosition
      } else {
        return { x: 0, y: 0 }
      }
    }
    Copy the code

    However, this does not work in the Better-Scroll list page, but re-entering other pages will return to the top, why is this?

    We have to figure out how these two scrolls work. When vue-Router is used, scrollBehavior records the scrolling of the entire page, that is, the page is automatically stretched due to too much content, and the height is not fixed. With better Scroll, the height of the wrapper is fixed and the elements inside the wrapper are rolled, so the whole page is not actually rolled, so the elements in the list are still in their original positions, but the page is actually back to the top.

    So how do I get the better scroll element back to the top? You can use the scrollToElement(this.$refs.Wrapper) method, or the scrollTo(0, 0) method.

    The above are some of my own experiences and experiences when using Better-Scroll. Welcome to point out if there are any improper ones.

    Reference: When Better scroll meets Vue