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

后端 未结 6 1371
温柔的废话
温柔的废话 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:00

    Behavior options aren't fully supported in IE/Edge/Safari, so you'd have to implement something on your own. I believe jQuery has something already, but if you're not using jQuery, here's a pure JavaScript implementation:

    function SmoothVerticalScrolling(e, time, where) {
        var eTop = e.getBoundingClientRect().top;
        var eAmt = eTop / 100;
        var curTime = 0;
        while (curTime <= time) {
            window.setTimeout(SVS_B, curTime, eAmt, where);
            curTime += time / 100;
        }
    }
    
    function SVS_B(eAmt, where) {
        if(where == "center" || where == "")
            window.scrollBy(0, eAmt / 2);
        if (where == "top")
            window.scrollBy(0, eAmt);
    }
    

    And if you need horizontal scrolling:

    function SmoothHorizontalScrolling(e, time, amount, start) {
        var eAmt = amount / 100;
        var curTime = 0;
        var scrollCounter = 0;
        while (curTime <= time) {
            window.setTimeout(SHS_B, curTime, e, scrollCounter, eAmt, start);
            curTime += time / 100;
            scrollCounter++;
        }
    }
    
    function SHS_B(e, sc, eAmt, start) {
        e.scrollLeft = (eAmt * sc) + start;
    }
    

    And an example call is:

    SmoothVerticalScrolling(myelement, 275, "center");
    
    0 讨论(0)
  • 2020-11-29 08:01

    Use smootscroll polyfill (solution for all browsers), easy applicable and lightweight dependency: https://github.com/iamdustan/smoothscroll

    Once you install it via npm or yarn, add it to your main .js, .ts file (one which executes first)

    import smoothscroll from 'smoothscroll-polyfill';
    // or if linting/typescript complains
    import * as smoothscroll from 'smoothscroll-polyfill';
    
    // kick off the polyfill!
    smoothscroll.polyfill();
    
    0 讨论(0)
  • 2020-11-29 08:03

    The workarounds above all make up for the lack of Safari support for behaviors.

    It's still necessary to detect when a workaround is needed.

    This little function will detect if smooth scrolling is supported by the browser. It returns false on Safari, true on Chrome and Firefox:

    // returns true if browser supports smooth scrolling
    const supportsSmoothScrolling = () => {
      const body = document.body;
      const scrollSave = body.style.scrollBehavior;
      body.style.scrollBehavior = 'smooth';
      const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
      body.style.scrollBehavior = scrollSave;
      return hasSmooth;
    };
    
    
    0 讨论(0)
  • 2020-11-29 08:04

    Combining the answers of George Daniel and terrymorse, the following can be used for all the browser's support using native JavaScript.

    As, Chrome, Firefox supports CSS, scroll-behavior: smooth; for the browsers which don't support this property, we can add below.

    HTML:

    <a onclick="scrollToSection(event)" href="#section">
        Redirect On section
    </a>
      
    <section id="section">
      Section Content
    </section>
    

    CSS:

    body {
      scroll-behavior: smooth;
    }
    

    JavaScript:

    function scrollToSection(event) {
      if (supportsSmoothScrolling()) {
        return;
      }
      event.preventDefault();
      const scrollToElem = document.getElementById("section");
      SmoothVerticalScrolling(scrollToElem, 300, "top");
    }
    
    function supportsSmoothScrolling() {
      const body = document.body;
      const scrollSave = body.style.scrollBehavior;
      body.style.scrollBehavior = 'smooth';
      const hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth';
      body.style.scrollBehavior = scrollSave;
      return hasSmooth;
    };
     
    function SmoothVerticalScrolling(element, time, position) {
      var eTop = element.getBoundingClientRect().top;
      var eAmt = eTop / 100;
      var curTime = 0;
      while (curTime <= time) {
        window.setTimeout(SVS_B, curTime, eAmt, position);
        curTime += time / 100;
      }
    }
    
    function SVS_B(eAmt, position) {
      if (position == "center" || position == "")
      window.scrollBy(0, eAmt / 2);
      if (position == "top")
      window.scrollBy(0, eAmt);
    }
    
    0 讨论(0)
  • 2020-11-29 08:15

    A simple jQuery fix that works for Safari:

    $('a[href*="#"]').not('[href="#"]').not('[href="#0"]').click(function (t) {
        if (location.pathname.replace(/^\//, "") == this.pathname.replace(/^\//, "") && location.hostname == this.hostname) {
            var e = $(this.hash);
            e = e.length ? e : $("[name=" + this.hash.slice(1) + "]"), e.length && (t.preventDefault(), $("html, body").animate({
                scrollTop: e.offset().top
            }, 600, function () {
                var t = $(e);
                if (t.focus(), t.is(":focus")) return !1;
                t.attr("tabindex", "-1"), t.focus()
            }))
        }
    });
    
    0 讨论(0)
  • 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);
    
    0 讨论(0)
提交回复
热议问题