The author is really lazy and this article has been around for months since the idea started. 😊

What is the iScroll

In many scenarios where body scrolling would be inconvenient, local scrolling of an element would be used, and nasty things would happen.

  1. On the PC Web, ugly scroll bars appear in Windows browsers. (In fact, there are also the latest CSS style can be solved, compatibility is not good)
  2. Mobile phone mobile terminal, ios browser can not be inertial and elastic sliding (default browser scrolling, non-ios system native scrolling), if added-webkit-overflow-scrolling: touch;Use system native scrolling, compatibility is not good, the bug is not one or two 😭.
  3. It is not conducive to the implementation of some personalized requirements, such as load, refresh, fit scrolling, etc.

Coincidentally, iScroll solves these problems.

IScroll’s author is an international friend, and his github ticket is here.

Unfortunately, the author hardly maintains the iScroll plugin any more, and there are few Chinese documents on the web, but that doesn’t stop us from carrying the plugin around.

So how does iScroll work

IScroll uses CSS3’s Transform animation to simulate inertial and elastic scrolling, and the effect and performance are perfectly close to the native scrolling effect. At the same time provides a lot of functions including custom scroll bar, specified scroll to elements and other functions, but also easy to achieve drop-down refresh, drop-down load.

Basic use of iScroll

It must have been installed first
npm install iscroll

yarn add iscroll
Copy the code
Then the reference
import IScroll from 'iscroll/build/iscroll'; / / normal version
import IScroll from 'iscroll/build/iscroll-probe'; / / complex version
import IScroll from 'iscroll/build/iscroll-infinite';
Copy the code

Iscroll has several different JS files, which are normal version, complex version, infinite scroll version. Here is commonly used is the complex version, is to support real-time monitoring of the scroll position, if you do not need real-time monitoring, you can use the normal version.

Initialization use

Here, take the VUE framework as an example

<template>
  <div class="wrap">
    <div class="scroll-area">
      <div v-for="n in 50" class="item">{{ n }}</div>
    </div>
  </div>
</template>
<script>
  import IScroll from 'iscroll/build/iscroll-probe';
  
  export default {
    data() {
      scroll: null,
    },
    mounted() {
      // Hint that since the transform operates on the DOM, it needs to operate in this lifecycle
      this.scroll = new IScroll('.wrap', {
        mouseWheel: true.// Allow mouse wheel
      });
      // The first argument is a DOM selector. It is recommended to use a unique ID, using class as an example
      // The second argument is the parameter object, which is some configuration of iscroll
      / / configuration parameters can refer to http://wiki.jikexueyuan.com/project/iscroll-5/}}</script>
<style>
  .wrap{
    height: 400px;
    overflow: hidden;
    /* Fix the scrollable height of the scrollable area, and beyond the hidden */
  }
</style>
Copy the code

This code completes the simple iscroll initialization, and you can see the effect

IScroll refresh

Note that the scrolling content may be asynchronously acquired and loaded in the DOM. If iscroll is not refreshed, the scrolling function may be affected. Therefore, after the asynchronous content is loaded, you need to call the refresh method to refresh IScroll

<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div v-for="n in 50" class="item">{{ n }}</div>
    </div>
  </div>
</template>
<script>
  import IScroll from 'iscroll/build/iscroll-probe';
  
  export default {
    data() {
      scroll: null,
    },
    mounted() {
      const el = this.$refs.scroll;
      this.scroll = new IScroll('.wrap', {... });// Update data asynchronously
      getData().then(_= >{
        this.scroll.refresh();
      })
      // Refresh when sliding for the first time
      el.addEventListener('touchstart', _ = >this.scroll.refresh()); }}</script>
Copy the code

Listening position

this.scroll = new IScroll('.wrap', {
  probeType: 3.// Scroll listener level has 3 levels, 3 is pixel level listener
});
// Register the Scroll event with iscroll instance
this.scroll.on('scroll', e => {
  // Instead of using arrow functions, you can use this.x and this.y to access real-time positions
  // this.scroll.x
  // this.scroll.y
})
Copy the code

More do not say, see the effect

Pay attention to the positive and negative values. The monitored value is the value of transform. Confirm the direction corresponding to the positive and negative values.

Scroll to the specified element position

Here you need to use iscroll’s bonding function

this.scroll = new IScroll('.wrap', {
  snap: '.item'});// When the snap attribute is set to true, iscroll splits the container's viewable area into a page
// When the snap attribute is set to the element selector, iscroll sets the corresponding element to a page
// here we set it to '.item'
Copy the code

Then use iscroll’s goToPage method to jump to the corresponding element

this.scroll.goToPage(0.30.1000);
// The parameters are x, y, animation time,
// Note that x,y are incoming indexes, the first is 0, and so on
Copy the code

You can also jump to one or the next using the prev and next methods

this.scroll.prev();
this.scroll.next();
Copy the code

Configuring scroll bars

If you want a scroll bar, it’s easy

this.scroll = new IScroll('.wrap', {
  scrollbars: true.// Open the scroll bar
  shrinkScrollbars: 'scale'.// Scale the scrollbar beyond the scroll
});
Copy the code
/* Since iscroll's scrollbar is a positioning implementation, the container needs to add a relative positioning */
.wrap{
  position: relative;
}
Copy the code

Click on the event

Iscroll disables the click event by default, or can be enabled if desired

this.scroll = new IScroll('.wrap', {
  click: true});Copy the code

And iscroll is very user-friendly built-in tap events, as long as tap is enabled, you can respond to tap elements

this.scroll = new IScroll('.wrap', {
  tap: true});Copy the code
<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div v-for="n in 50" class="item" @tap="onTap">{{ n }}</div>
    </div>
  </div>
</template>
Copy the code

The sticky iScroll

In native scrolling based on DOM elements, it is possible to add position: sticky to the content to achieve the top sucking effect.

Top sucking: In the scrolling process of the parent element, if the child element has position: sticky and top: 0 styles, the content will stick to the top of the parent element when rolled to the top, and will not continue to scroll up. (Same with horizontal scrolling)

If you haven’t played Position: sticky, go and try it. Of course, this CSS is not invincible, because the compatibility is not up to standard. Stamp here

If iscroll uses transform to roll, overflow is set: Hidden, so there is no CSS sticky implementation, so since the parent element is the transform scroll, then when the top position is reached, the child element reverse transform will be ok?

Look carefully at the following code, very important, look carefully at the comments ️

// This code can be interpreted as an extension of the iscroll class
// The argument here is the iscroll class
export const extendSticky = (iScroll) = > {
  let m = Math;
  // Here is the browser CSS prefix configured for compatibility, there are many ways to write it on the web
  let vendor = (/webkit/i).test(navigator.appVersion) ? 'webkit' :
      (/firefox/i).test(navigator.userAgent) ? 'Moz' :
        'opera' in window ? 'O' : ' ',
    has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(),
    trnOpen = 'translate' + (has3d ? '3d(' : '('),
    trnClose = has3d ? ', 0) ' : ') ';
  
  * @param selector a collection of objects that require sticky elements and sticky positions * @return {iScrollStickyHeaders} */
   // Add the enableStickyHeaders method to the iscroll prototype
  iScroll.prototype.enableStickyHeaders = function (selector) {
    return new iScrollStickyHeaders(this, selector); // Extend the method to take the new class and pass the parameter
  };

  // argument, iscroll instance, set of elements that need sticky
  let iScrollStickyHeaders = function (iscroll, selector) {
    if(! iscroll.options.useTransform) {return;
    }
    this.iscroll = iscroll;
    this.selector = selector;
    this.initialize(); / / initialization
  };
  iScrollStickyHeaders.prototype = {
    headers: [].// Store a collection of sticky objects
    initialize() {
      let that = this;
      this._augment();
      this.iscroll.on('refresh'.function() {
        that._refresh() // Every time iscroll is refreshed, the sticky method is also refreshed
      });
      this.iscroll.refresh()
    },
    _refresh() { // Initialize or refresh
      let elms = this.selector;
      this.headers = [ // Deep copy object collection. elms, ]{el: element, top: position where sticky is needed}
      // You can customize the format and logic code as you like
      this._translate(0.0); / / initialization
    },
    _augment() { // Initialize the function
      let that = this;
      this.iscroll.on('scroll'.function() {
        that._translate(this.x, this.y) // iscroll triggers the main function when scrolling
      });
      this.iscroll.on('beforeScrollStart'.function() {
        that._translate(this.x, this.y) Trigger the main function when iscroll is about to scroll
      });
      this.iscroll.on('scrollStart'.function() {
        that._translate(this.x, this.y) When iscroll starts scrolling, the main function is triggered
      });
    },
    _translate(x, y) { // Reverse transform when the sticky position is reached
      let absY = m.abs(y); // Get the absolute value of the Y-axis roll
      this.headers.forEach((stickyObj) = > { // Traverse the sticky object
        let translateY = 0; // Reverse transform sticky defaults to 0
        let yy = m.abs(absY - stickyObj.el.offsetTop); // Calculate iscroll's y-scroll value - the current element's distance from its parent
        // Stickyobj.el.offsetTop is fixed
        Yy is the position of the current element from the top of the container
        // absY < stickyobj.el.offsetTop indicates that the element has not reached the top yet
        // yy <= stickyobj. top Determines whether the element reaches the sticky position
        (1) If the element does not reach the top of the container, the value is 0 by default
        If the value is sticky, the value is still 0
        // If the sticky position is reached, the reverse transform distance is calculated after the sticky position is exceeded
        // By default, the specified position is less than the initial position of the element.
        if (absY - stickyObj.el.offsetTop > 0 || yy <= stickyObj.top) {
          // This formula needs to be understood again and again
          // When the container rolls up, the container's transform is negative, so our reverse is positive
          // If the container rolls up to absY, we will roll down to transform if we are sticky
          // stickyobj.el.offsetTop - stickyobj.top is how much the container has to scroll to get the element to the sticky position
          // Calculate iscroll container scroll value - (initial position - specified position)
          // When the scroll value is equal to the difference between the initial position and the specified position, it is exactly 0
          // As the scroll value gets larger and larger, any value that exceeds 0 is the value that requires the reverse transform
          translateY = absY - (stickyObj.el.offsetTop - stickyObj.top);
        } else {
          translateY = 0;
        }
        // Finally concatenate the browser prefix to complete the CSS assignment
        stickyObj.el.style[vendor + 'Transform'] = trnOpen + ('0,' + translateY + 'px') + trnClose; }); }}; };export default extendSticky;
Copy the code

To help you understand, let me show you Axure’s prowess.

Now that the iscroll-sticky. Js tool is complete, let’s use it.

<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div v-for="n in 20" class="item">{{ n }}</div>
      <div ref="sticky" class="sticky" :top="20">21</div>
      <div v-for="n in 20" class="item">{{ n+20 }}</div>
    </div>
  </div>
</template>
<script>
  import IScroll from 'iscroll/build/iscroll-probe';
  import enableSticky from 'path/to/iscroll-sticky.js';
  enableSticky(IScroll); // This step is to mount the sticky method to the iscroll prototype
  
  export default {
    data() {
      scroll: null,
    },
    mounted() {
      const el = this.$refs.scroll;
      this.scroll = new IScroll('.wrap', {... });const stickyEl = this.$refs.sticky;
      // Allow element object set sticky
      this.scroll.enableStickyHeaders([
        {
          el: stickyEl,
          top: stickyEl.getAttribute('top') // Here I set top to the native prop}]); }}</script>
Copy the code

Let’s see what happens.

Iscroll-sticky. Js is a flexible js that can be configured and modified according to your own needs.

The drop-down refresh

Iscroll doesn’t actually have a pull-down refresh function, but it does.

export default {
  data() {
    scroll: null.status: 0.// Use a variable to record the iscroll scrolling status. The default is 0
    txt: 'Pull refresh'.// Record the refresh text, default
  },
  watch: {
    status() {
      // Every time iscroll's status code changes, iscroll is refreshed so that IScroll recalculates dom elements
      this.iscroll.refresh(); }}}Copy the code

Then add a refresh text (or animation)

<template>
  <div ref="scroll" class="wrap">
    <div class="scroll-area">
      <div :class="{hide: status===0}" class="refresh">{{ txt }}</div>
      <div v-for="n in 50" class="item">{{ n }}</div>
    </div>
  </div>
</template>
Copy the code
.refresh{
  width: 100%;
  height: 50px;
  line-height: 50px;
  text-align: center;
  &.hide{
    /* When status is 0 by default, hide the refresh text by positioning it outside the container */
    position: absolute;
    left: 0;
    top: -50px; }}Copy the code

Next listen for the iscroll drop-down

// ...
this.scroll.on('scroll', e => {
  const y = this.iscroll.y; // Listen for the y value of the pull-down, which is positive
  if (y >= 50) { // Pull-down distance >= refresh text height,
    this.status = 1; // The status code changes to 1, indicating that it is ready to refresh}})Copy the code

At this point, the status value changes to 1, so that the updated text we hide has become normal content to load iscroll. The dom change here needs to be understood clearly, the key is that iscroll will be refreshed after the state change, our finger is not released, so we are ready to refresh the state. A new listener is needed to listen for the finger to leave and scroll to stop.

this.scroll.on('scroll', e => {
  const y = this.iscroll.y; // Listen for the y value of the pull-down, which is positive
  if (y >= 50) { // Pull-down distance >= refresh text height,
    this.txt = 'Release refresh';
    this.status = 1; // The status code changes to 1, indicating that it is ready to refresh
  } else if (y > 0) { // If it returns and does not want to refresh, restore status to 0
	this.txt = 'Pull refresh';
	this.status = 0; }})this.scroll.on('scrollEnd', e => {
  if (status === 1) { // When scrolling stops, if it is ready to refresh
    this.txt = 'Refresh... ';
    this.status = 2; // Change the status code to start the refresh
    this.scroll.disable(); // The refresh process disables scrolling, depending on your needs.
    this.updateData(); // Suppose there is a method that updates the data}})Copy the code
export default {
  methods: {
    updateData() {
      getData().then(_= >{
        // Data update completed
        this.txt = 'Refresh complete';
        // Continue to hide refreshed text after a 1 second delay
        setTimeout(_= >{
          this.txt = 'Pull refresh';
          this.status = 0; // The status is reset to 0
          this.scroll.enable();
        }, 1000); })}}}Copy the code

Take a look at the demo:

Attached here is a cat eye movie demo I usually do:

Pull on loading

This is also to implement their own, but this is very simple, determine the bottom of the scroll can be.

this.scroll.on('scroll', e => {
  // scrollEl is the container height, and contentEl is the content height. Because y is negative, scrollel-contentel is used
  if (this.scroll.y <= scrollEl.offsetHeight - contentEl.offsetHeight) {
    // do something pull-up}});Copy the code

conclusion

Iscroll is a very flexible library that can be configured according to the desired effect.

If you are familiar with modularity, try wrapping sticky, pull-down refresh, and pull-up load into a component.

Comment someone mentioned better scroll, no problem, use whichever you like.

Welcome to click like collection, the follow-up and IScroll related will be updated in time.