Javascript - window.scroll({ behavior: 'smooth' }) not working in Safari

后端 未结 6 1382
温柔的废话
温柔的废话 2020-11-29 07:54

As the title says, it works perfectly fine on Chrome. But in Safari, it just sets the page to the desired top and and left position. Is this an expected behaviour? Is there

6条回答
  •  孤独总比滥情好
    2020-11-29 08:19

    The solution with the smoothest performance, especially if you want to incorporate easing is to use requestAnimationFrame:

    const requestAnimationFrame = window.requestAnimationFrame ||
              window.mozRequestAnimationFrame ||
              window.webkitRequestAnimationFrame ||
              window.msRequestAnimationFrame;
    
    const step = (timestamp) => {
      window.scrollBy(
        0,
        1, // or whatever INTEGER you want (this controls the speed)
      );
    
      requestAnimationFrame(step);
    };
    
    
    requestAnimationFrame(step);
    

    if you want to later cancel the scroll, you need to have a reference to your requestAnimationFrame (do this everywhere you use requestAnimationFrame(step)):

    this.myRequestAnimationFrame = requestAnimationFrame(step);
    
    const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
    cancelAnimationFrame(this.myRequestAnimationFrame);
    

    Now what if you want to use easing with your scroll and take timeouts between scroll actions?

    create an array of 60 elements (requestAnimationFrame usually calls 60 times per second. It's technically whatever the refresh rate of the browser is, but 60 is the most common number.) We are going to fill this array non-linearly then use those numbers to control how much to scroll at each step of requestAnimationFrame:

    let easingPoints = new Array(60).fill(0)
    

    choose an easing function. Let's say we're doing a cubic ease-out:

    function easeCubicOut(t) {
        return --t * t * t + 1;
    }
    

    create a dummy array and fill it with data piped through the easing function. You'll see why we need this in a moment:

        // easing function will take care of decrementing t at each call (too lazy to test it at the moment. If it doesn't, just pass it a decrementing value at each call)
        let t = 60;
        const dummyPoints = new Array(60).fill(0).map(()=> easeCubicOut(t));
        const dummyPointsSum = dummyPoints.reduce((a, el) => {
                                    a += el;
                                   return a;
                               }, 0);
    

    map easingPoints using the help of each dummyPoint ratio to dummyPointsSum:

        easingPoints = easingPoints.map((el, i) => {
            return Math.round(MY_SCROLL_DISTANCE * dummyPoints[i] / dummyPointsSum);
        });
    

    in your scroll function, we'll make a few adjustments:

         const requestAnimationFrame = window.requestAnimationFrame ||
                  window.mozRequestAnimationFrame ||
                  window.webkitRequestAnimationFrame ||
                  window.msRequestAnimationFrame;
    
         let i = 0;
         const step = (timestamp) => {
           window.scrollBy(
             0,
             easingPoints[i],
           );
    
    
            if (++i === 60) {
                    i = 0;
                    return setTimeout(() => {
                      this.myRequestAnimationFrame = requestAnimationFrame(step);
                    }, YOUR_TIMEOUT_HERE);
            }
          };
    
    
          this.myRequestAnimationFrame = requestAnimationFrame(step);
    

提交回复
热议问题