My previous personal website was built from the beta version of VuePress. It was fresh at the time, but now it seems to be too old. I decided to write another one. Want to achieve a hijacking mouse wheel on their website, the effect of turning pages, record the road to step on the pit. Let’s take a look at the official website of the imitation target tomorrow ark, remember that xiaomi and one Plus mobile phone introduction page also used this scroll

Final effect ✌️

Static Page layout

A very simple CSS property called background-attachment is all that is needed to make the banner image not scroll with the content. Of course, you can also directly in the img tag through the posistion attribute to fix, but need to set other attributes such as margin, Z-index, or not very convenient.

<! -- demo -->
<div class="banner"></div>
<div style="height: 100vh;"></div>

<style lang="scss">
  .banner {
    height: 80vh;
    background-image: linear-gradient(# 000000.#ffffff);
    /* Keep the background image without scrolling with the content */
    background-attachment: fixed;
  }
</style>
Copy the code

Intercept the mouse wheel default event

With the background fixed, the next step is to intercept the default mouse wheel event and implement its own page turning effect, which is a very simple addEventListener

window.addEventListener('mousewheel', scrollFn)

function scrollFn (e) {
  e.preventDefault()
  if (e.deltaY > 0) {
    // Scroll down...
  } else {
    // Scroll up...}}Copy the code

But the error was reported in the browser, and the default mouse wheel event still fires.

Click the Chrome feature and find that there is an additional passive attribute of Event, and the default passive attribute of WheelEvent is true. Then click the MDN of addEventListener and find that

The option of “options” is added. Because the useCapture property of addEventListener is so rarely used, it has been specified as an optional property by the end of 2015 and can be passed to objects. Passive: prevent listener preventDefault() change to window.addeventListener (‘mousewheel’, this.scrollfn, {passive: False}) successfully implement the wheel default event

First attempt: scroll-behavior: smooth

Using the CSS property scroll-behavior: smooth is the easiest, easiest, and much better than the second one, but Safari on MAC doesn’t support this feature, and safari on ios does support 😓

function scrollFn (e: WheelEvent) {
  e.preventDefault()
  // Get the window height
  const windowHeight = window.innerHeight || document.body.clientHeight
  // Calculate the height of the banner. The CSS property is 80vh
  const scrollHeight = windowHeight * 0.8
  window.scrollTo(0, e.deltaY > 0 ? scrollHeight : 0)}Copy the code



A more convenient method was found when searching for similar properties in Safariwindow.scrollTo({ top: 1000, behavior: "smooth" })I tried it, and Safari on MAC doesn’t support it either.

Second attempt: requestAnimationFrame

In order to be compatible with Safari, you still have to hand-write the page turn effect. Scroll animation is performed using the requestAnimationFrame method, calling the animation function before the browser redraws. Calls are usually made based on the number of frames on the display, and can stop animation while the page is blur, saving resources.

function scrollFn (e: WheelEvent) {
  e.preventDefault()
  let start = 0
  // Animation functions require closure access to start
  const step = (unix: number) = > {
    if(! start) { start = unix }const duration = unix - start
    // Get the window height
    const windowHeight = window.innerHeight || document.body.clientHeight
    // Calculate the height of the banner. The CSS property is 80vh
    const scrollHeight = windowHeight * 0.8
    // The distance that should be rolled at the current time
    const nowY = scrollHeight / 1000 * duration
    window.scrollTo(0, e.deltaY > 0 ? nowY : scrollHeight - nowY)
    // 1000ms
    if (duration < 1000) {
      requestAnimationFrame(step)
    }
  }
  requestAnimationFrame(step)
}
Copy the code

And when I’m done with the code, it’s a little bit of a crotch pull and this linear effect that goes up and down, it’s too stupid

Third attempt: Add easing function

After a search in the search engine, most of them are math problems and canvas, and I also turned over a lot of analysis about The Besser curve, the difficulty is still a little high (wrong direction 😅). When I thought I could only give up my own handwriting and use the power of NPM, I finally found this website in a pile of math problems, which saved my life 🐶

In real life, objects don’t start or stop suddenly, and they certainly don’t keep moving at a constant speed. Just like when we open a drawer, the initial pull is quick, but when the drawer is pulled out we unconsciously slow down. When something falls on the floor, it starts to fall very fast, and then it bounces back and forth on the floor until it stops.

By using the relaxation function in the website, it can simulate the real environment to some extent, to add real effect to the animation

function scrollFn (e: WheelEvent) {
  e.preventDefault()
  // Scrolling, or the last page is still rolling down, or the first page is still rolling up
  if (this.debounce || (e.deltaY > 0 && this.index >= 1) || (e.deltaY < 0 && this.index === 0)) {
    return
  }
  let start = 0
  // Animation functions that require closure access to start are not separated
  const step = (unix: number) = > {
    if(! start) { start = unix }const duration = unix - start
    // Get the window height
    const windowHeight = window.innerHeight || document.body.clientHeight
    // Calculate the height of the banner. The CSS property is 80vh
    const scrollHeight = windowHeight * 0.8
    Duration / 1000 increments between 0 and 1 within 1000ms and returns the same value, multiplied by the final height
    const y = this.easeInOutCubic(duration / 1000) * scrollHeight
    window.scrollTo(0, e.deltaY > 0 ? y : scrollHeight - y)
    if (duration <= 1001) {
      requestAnimationFrame(step)
      this.debounce = true
    } else {
      this.debounce = false
      e.deltaY > 0 ? this.index++ : this.index--
      console.log(this.index)
    }
  }
  requestAnimationFrame(step)
}

// Ease function, x range 0-1, return number 0-1 https://easings.net#easeInOutCubic
function easeInOutCubic (x: number) :number {
  return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2.3) / 2;
}
Copy the code

EaseInOutCubic will output from 0% to 100% depending on the range 0-1 of x passed in, asin the easings.net#easeInOutCubic example, Control the speed of the requestAnimationFrame

Effect of contrast

From 👎 to 👍

requestAnimationFrame

scroll-behavior: smooth

requestAnimationFrame with easings

conclusion

The project address

  • WheelEvent preventDefault: passive is true, do not allow preventDefault
  • scroll-behavior: smoothMost convenient, with ease function, but Safari does not support, complete animation time is not controlled
  • RequestAnimationFrame is a linear animation, and the larger the scope of the animation, the more dull it looks. The requestAnimationFrame needs a slow function to control the speed, but you can control the time to complete the animation yourself
  • Lazy, did not put in the scrolling of the tremble, according to the page position can continue to scroll these complex judgment