preface

After studying the smooth rolling for 2 days, and then combining with the implementation of anchor points, I feel a lot of harvest, so I wrote down a record to sort it out.

The most common requirement is that a longer page might have a button in the lower-right corner that you click to go back to the top. This is usually achieved with anchor points, but the disadvantage of native anchor points is direct jump, too blunt.

So we need an implementation of smooth scrolling.

Note: When using Codepen, the codepen browser environment is your browser environment, so if the following example does not work, please check your browser environment.

Using CSS

For newer browsers, it’s finally time for a native CSS implementation that no longer requires JS for smooth scrolling.

The CSS property scroll behavior applies to scrolling triggered by navigation or the CSSOM scrolling APIs. When the value is smooth, click on the A TAB of the corresponding anchor point, and a smooth, uniform scroll will appear to the corresponding anchor point position. Because anchor behavior is triggered by the CSSOM scrolling APIs

Learn about scroll behavior in MDN

For this to work across HTML, we just need CSS to write:

html {
  scroll-behavior: smooth; 
}
Copy the code

Note: Using this attribute on body is not valid, see MDN

CodePen on sample

Chrome has supported this property since Chrome 61(2017.9), so this property is relatively new and only supported by newer versions of Chrome, Firefox, and Opera. Safari, Edge, and Any version of IE are not supported.

Use Windows. ScrollTo ()

Window.scrollto can be used in two ways

window.scrollTo(x-coord, y-coord)
window.scrollTo(options)
Copy the code

The first use is just like a regular anchor point, which immediately rolls into position. Only the second usage can achieve smooth scrolling, where the behavior “smooth” passed in options can achieve uniform smooth scrolling

window.scrollTo({
  top: 100.// The position of the scroll end y
  left: 100.// The position of the end of the scroll x
  behavior: 'smooth' // Smooth scrolling
});
Copy the code

CodePen on sample

Unfortunately, the second usage is also less compatible.

Only newer versions of Chrome, Firefox and Opera are supported. Safari, Edge, and Any version of IE are not supported.

Use the requestAnimationFrame implementation

When neither of the above two native methods is available, we need to use js implementation manually.

RequestAnimationFrame is an API that browsers provide for drawing animations. It will be executed before each frame of the browser Layout, Paint, repaint, etc., so that you can easily execute a piece of code that changes the style, and then the browser can immediately redraw, redraw, and generate a frame.

Use the benefits of requestAnimationFrame

For now, it is better to use the requestAnimationFrame implementation

What are the benefits of using the requestAnimationFrame implementation?

  1. RequestAnimationFrame performs better than the setInterval implementation in the past. RequestAnimationFrame guarantees backflow redraw after each style change, setInterval may cause the browser to make invalid backflow and redraw. RequestAnimationFrame fires during the lifetime of each frame to make the animation smoother, whereas setInterval cannot guarantee that it fires every frame.

  2. RequestAnimationFrame is very compatible and is compatible all the way down to IE10. For IE9 and below, you can degrade polyfill of requestAnimationFrame using setTimeout or setInterval.

  3. Write your own can achieve a variety of different rolling speed, can achieve linear speed, can also achieve the effect of speeding up and then slowing down.

Raf achieves a uniform roll

Here is an example of trying to use the requestAnimationFrame implementation.

Along with this example, I’ll introduce you to the use of requestAnimationFrame

First of all, through the document. The documentElement. ScrollTop get the current page is the position of the scroll bar.

Document. The documentElement. ScrollTop represents the distance of the scroll bar to the top of the page, when the document. The documentElement. The scrollTop = = = 0, illustrate the scroll bar at the top of the page,

document.documentElement.scrollTop //
document.documentElement.scrollTop = 0 // Assigning a value to it will immediately scroll the scroll bar to the top of the page
Copy the code

Let’s use requestAnimationFrame for the simplest example of uniform scrolling:

<div id="button">Back to the top</div>
Copy the code
let button = document.getElementById('button')
button.addEventListener('click'.function(){
  let cb = (a)= >{
    if(document.documentElement.scrollTop===0) {// No more recursive calls
      return;
    } else {
      let speed = 40 // The distance the scrollbar moves within each frame
      document.documentElement.scrollTop -= speed
      // If the condition is not met, call cb again
      requestAnimationFrame(cb)
    }
  }
  requestAnimationFrame(cb)
}
Copy the code

RequestAnimationFrame accepts a callback function that will be executed sometime in the next frame of the browser. Note that there is only the next frame, so it is necessary to judge the condition in the callback function, and pass the callback function into RAF again when the condition is not met

You can see this example at codePen: codePen

In the example, uniform rolling is realized, and the rolling speed can be controlled according to variables.

Next, consider a feature where clicking a button takes you back to the top of the page in about 2 seconds, no matter where you are in the scroll bar. How would that work? And the uniform motion of the animation appears a little stiff, can achieve the first acceleration and then deceleration function?

Raf realizes variable speed rolling to the top and controls the rolling time to 2 seconds

1. How to achieve the scrolling position within 2 seconds?

In general, the most widely used hardware refresh rate today is 60 frames, and it doesn’t make sense for a browser to perform a backflow redraw between hardware 2 frames. Therefore, the duration of a frame maintained by the browser is 16.7ms, which can be roughly considered as 1/60s of a frame, and 120 consecutive calls to Raf (CB) are approximately 2 seconds.

2. How to achieve variable speed rolling?

Rolling is a certain distance, make it as short, we need to make the document in 120 frames. The documentElement. ScrollTop value reduced to 0.

const distance = document.documentElement.scrollTop
Copy the code

In this animation, the speed is the number subtracted from the scrollTop in one frame. Understanding this allows us to implement a variable speed roll with an arithmetic sequence.

// Assume the minimum step size is x1framedocument.documentElement.scrollTop - x * 12framedocument.documentElement.scrollTop - x * 23framedocument.documentElement.scrollTop - x * 3. The first60framedocument.documentElement.scrollTop - x * 6061framedocument.documentElement.scrollTop - x * 60 // The speed reaches its peak62framedocument.documentElement.scrollTop - x * 5963framedocument.documentElement.scrollTop - x * 58. The first120framedocument.documentElement.scrollTop - x * 1
Copy the code

so

let x = distance / (1+2+3... +60+60+59+... +2+1) is equivalent tolet x = distance / 60 / 61
Copy the code

The following complete JS code, while counting the time of the scroll.

let button = document.getElementById('button')

let dateBegin; 
let dateEnd;
button.addEventListener('click'.function(){
  dateBegin = Date.now()
  let current = document.documentElement.scrollTop
  // Use promise to count the time
  new Promise((resolve,reject) = >{
    let unit = current/60/61 // Minimum step size
    let index = 0
    let cb = (a)= >{
      if(document.documentElement.scrollTop===0){
        dateEnd = Date.now()
        resolve([dateBegin,dateEnd])
      } else{
        index++
        if(index<=60){
          current-=index*unit 
          / / this place do not use the document. The documentElement. ScrollTop instead of the current,
          / / the document. The documentElement. ScrollTop can accept floating point Numbers, then converted to an integer
          // This will change the time
        } else if(index>60){
          current-=(121-index)*unit
        }
        document.documentElement.scrollTop = current
        requestAnimationFrame(cb)
      }
    }
    requestAnimationFrame(cb)
  }).then((data) = >{
    console.log(data[1] - data[0].' ms')})})Copy the code

CodePen view codePen

The console output is around 1900ms, and it can be seen that the scrolling is accelerated first and then decelerated.

However, this implementation of time control depends on the device is guaranteed to refresh at 60 frames. What if the current page has a lot of animation rendering or complex JS calculation, so that the browser can not guarantee 60 frames?

In fact, the use of RAF here is rather rough. For example, RAF provides a time parameter for the incoming CB, which can be accurate to 5μs. Use this to accurately control the time of rolling. You can also add extensibility by calling cancelAnimationFrame to cancel execution of RAF, thus terminating scrolling during scrolling.

In the next part, I will analyze the source of the smooth-Scroll library, which has a wonderful implementation and supports a lot of variable speed scrolling and can be customized very well. In the VUE API documentation, clicking on the sidebar will smoothly roll the document to the corresponding API location, which is implemented using this library. I’ll leave it here for a second.

Final solution: use setInterval or setTimeout

This usage is arguably the most compatible method. When the browser does not support RAF, we can use Raf polyfill, which is implemented with setTimeout. This and RAF writing is about the same, not to repeat. When you need a browser that is compatible with raf and does not support raf, it is best to use Raf’s Polyfill, which ensures elegant degradation while still working better on modern browsers.