window.scrollTo with options not working on Microsoft Edge

前端 未结 7 1801
不知归路
不知归路 2020-12-03 21:07

I have a strange issue which I can only replicate on Microsoft browsers (Edge and IE11 tested).

相关标签:
7条回答
  • 2020-12-03 21:44

    The "smoothscroll" polyfill supports only "smooth" option. To support all options in scrollIntoViewOptions it's better to use seamless-scroll-polyfill (https://www.npmjs.com/package/seamless-scroll-polyfill)

    Worked for me.

    Here is a link with explanation https://github.com/Financial-Times/polyfill-library/issues/657

    0 讨论(0)
  • 2020-12-03 21:47

    Unfortunately, there is no way for that method to work in these two browsers. You can check open issues here and see that they have done nothing on this issue. https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/15534521/

    0 讨论(0)
  • 2020-12-03 21:48

    As mentioned before, the Scroll Behavior specification has only been implemented in Chrome, Firefox and Opera.

    Here's a one-liner to detect support for the behavior property in ScrollOptions:

    const supportsNativeSmoothScroll = 'scrollBehavior' in document.documentElement.style;
    

    And here's a simple implementation for cross-browser smooth scrolling: https://nicegist.github.io/d210786daa23fd57db59634dd231f341

    0 讨论(0)
  • 2020-12-03 21:48

    Maybe not a true answer in the sense of the word, but I have solved this problem by using this helpful polyfill: https://github.com/iamdustan/smoothscroll which works really well across all browsers.

    Example page for pollyfill: http://iamdustan.com/smoothscroll/

    Many thanks to the author.

    0 讨论(0)
  • 2020-12-03 21:53

    Indeed, they don't support this variant, MDN articles should be updated.

    One way to polyfill this method is to run the scroll method in a requestAnimationFrame powered loop. Nothing too fancy here.

    The main problem that arises is how to detect when this variant is not supported. actually @nlawson's answer tackles this problem perfectly...

    For this, we can use the fact that a call to Window#scroll will fire a ScrollEvent if the viewPort actually did scroll.
    This means we can set up an asynchronous test that will:

    1. Attach an event handler to the ScrollEvent,
    2. Call a first time scroll(left , top) variant to be sure the Event will fire,
    3. Overwrite this call with a second one using the options variant.
    4. In the event handler, if we aren't at the correct scroll position, this means we need to attach our polyfill.

    So the caveat of this test is that it is an asynchronous test. But since you need to actually wait for the document has loaded before calling this method, I guess in 99% of cases it will be ok.

    Now to less burden the main doc, and since it is already an asynchronous test, we can even wrap this test inside an iframe, which gives us something like:

    /* Polyfills the Window#scroll(options) & Window#scrollTo(options) */
    (function ScrollPolyfill() {
    
      // The asynchronous tester
    
      // wrapped in an iframe (will not work in SO's StackSnippet®)
      var iframe = document.createElement('iframe');
      iframe.onload = function() {
        var win = iframe.contentWindow;
        // listen for a scroll event
        win.addEventListener('scroll', function handler(e){
          // when the scroll event fires, check that we did move
          if(win.pageXOffset < 99) { // !== 0 should be enough, but better be safe
            attachPolyfill();
          }
          // cleanup
          document.body.removeChild(iframe);      
        });
        // set up our document so we can scroll
        var body = win.document.body;
        body.style.width = body.style.height = '1000px';
    
        win.scrollTo(10, 0); // force the event
        win.scrollTo({left:100, behavior:'instant'}); // the one we actually test
      };
      // prepare our frame
      iframe.src = "about:blank";
      iframe.setAttribute('width', 1);
      iframe.setAttribute('height', 1);
      iframe.setAttribute('style', 'position:absolute;z-index:-1');
      iframe.onerror = function() {
        console.error('failed to load the frame, try in jsfiddle');
      };
      document.body.appendChild(iframe);
    
      // The Polyfill
    
      function attachPolyfill() {
        var original = window.scroll, // keep the original method around
          animating = false, // will keep our timer's id
          dx = 0,
          dy = 0,
          target = null;
    
        // override our methods
        window.scrollTo = window.scroll = function polyfilledScroll(user_opts) {
          // if we are already smooth scrolling, we need to stop the previous one
          // whatever the current arguments are
          if(animating) {
            clearAnimationFrame(animating);
          }
    
          // not the object syntax, use the default
          if(arguments.length === 2) {
            return original.apply(this, arguments);
          }
          if(!user_opts || typeof user_opts !== 'object') {
            throw new TypeError("value can't be converted to a dictionnary");
          }
    
          // create a clone to not mess the passed object
          // and set missing entries
          var opts = {
            left: ('left' in user_opts) ? user_opts.left : window.pageXOffset,
            top:  ('top' in user_opts) ? user_opts.top : window.pageYOffset,
            behavior: ('behavior' in user_opts) ? user_opts.behavior : 'auto',
          };
          if(opts.behavior !== 'instant' && opts.behavior !== 'smooth') {
            // parse 'auto' based on CSS computed value of 'smooth-behavior' property
            // But note that if the browser doesn't support this variant
            // There are good chances it doesn't support the CSS property either...
            opts.behavior = window.getComputedStyle(document.scrollingElement || document.body)
                .getPropertyValue('scroll-behavior') === 'smooth' ?
                    'smooth' : 'instant';
          }
          if(opts.behavior === 'instant') {
            // not smooth, just default to the original after parsing the oject
            return original.call(this, opts.left, opts.top);
          }
    
          // update our direction
          dx = (opts.left - window.pageXOffset) || 0;
          dy = (opts.top - window.pageYOffset) || 0;
    
          // going nowhere
          if(!dx && !dy) {
            return;
          }
          // save passed arguments
          target = opts;
          // save the rAF id
          animating = anim();
    
        };
        // the animation loop
        function anim() {
          var freq = 16 / 300, // whole anim duration is approximately 300ms @60fps
            posX, poxY;
          if( // we already reached our goal on this axis ?
            (dx <= 0 && window.pageXOffset <= +target.left) ||
            (dx >= 0 && window.pageXOffset >= +target.left) 
          ){
            posX = +target.left;
          }
          else {
            posX = window.pageXOffset + (dx * freq);
          }
    
          if(
            (dy <= 0 && window.pageYOffset <= +target.top) ||
            (dy >= 0 && window.pageYOffset >= +target.top) 
          ){
            posY = +target.top;
          }
          else {
            posY = window.pageYOffset + (dx * freq);
          }
          // move to the new position
          original.call(window, posX, posY);
          // while we are not ok on both axis
          if(posX !== +target.left || posY !== +target.top) {
            requestAnimationFrame(anim);
          }
          else {
            animating = false;
          }
        }
      }
    })();
    


    Sorry for not providing a runable demo inside the answer directly, but StackSnippet®'s over-protected iframes don't allow us to access the content of an inner iframe on IE...
    So instead, here is a link to a jsfiddle.


    Post-scriptum: Now comes to my mind that it might actually be possible to check for support in a synchronous way by checking for the CSS scroll-behavior support, but I'm not sure it really covers all UAs in the history...


    Post-Post-scriptum: Using @nlawson's detection we can now have a working snippet ;-)

    /* Polyfills the Window#scroll(options) & Window#scrollTo(options) */
    (function ScrollPolyfill() {
    
      // The synchronous tester from @nlawson's answer
      var supports = false
        test_el = document.createElement('div'),
        test_opts = {top:0};
      // ES5 style for IE
      Object.defineProperty(test_opts, 'behavior', {
        get: function() {
          supports = true;
        }
      });
      try {
        test_el.scrollTo(test_opts);
      }catch(e){};
      
      if(!supports) {
        attachPolyfill();
      }
    
      function attachPolyfill() {
        var original = window.scroll, // keep the original method around
          animating = false, // will keep our timer's id
          dx = 0,
          dy = 0,
          target = null;
    
        // override our methods
        window.scrollTo = window.scroll = function polyfilledScroll(user_opts) {
          // if we are already smooth scrolling, we need to stop the previous one
          // whatever the current arguments are
          if(animating) {
            clearAnimationFrame(animating);
          }
    
          // not the object syntax, use the default
          if(arguments.length === 2) {
            return original.apply(this, arguments);
          }
          if(!user_opts || typeof user_opts !== 'object') {
            throw new TypeError("value can't be converted to a dictionnary");
          }
    
          // create a clone to not mess the passed object
          // and set missing entries
          var opts = {
            left: ('left' in user_opts) ? user_opts.left : window.pageXOffset,
            top:  ('top' in user_opts) ? user_opts.top : window.pageYOffset,
            behavior: ('behavior' in user_opts) ? user_opts.behavior : 'auto',
          };
        if(opts.behavior !== 'instant' && opts.behavior !== 'smooth') {
          // parse 'auto' based on CSS computed value of 'smooth-behavior' property
            // But note that if the browser doesn't support this variant
            // There are good chances it doesn't support the CSS property either...
          opts.behavior = window.getComputedStyle(document.scrollingElement || document.body)
            .getPropertyValue('scroll-behavior') === 'smooth' ?
              'smooth' : 'instant';
        }
        if(opts.behavior === 'instant') {
            // not smooth, just default to the original after parsing the oject
            return original.call(this, opts.left, opts.top);
          }
    
          // update our direction
          dx = (opts.left - window.pageXOffset) || 0;
          dy = (opts.top - window.pageYOffset) || 0;
    
          // going nowhere
          if(!dx && !dy) {
            return;
          }
          // save passed arguments
          target = opts;
          // save the rAF id
          animating = anim();
    
        };
        // the animation loop
        function anim() {
          var freq = 16 / 300, // whole anim duration is approximately 300ms @60fps
            posX, poxY;
          if( // we already reached our goal on this axis ?
            (dx <= 0 && window.pageXOffset <= +target.left) ||
            (dx >= 0 && window.pageXOffset >= +target.left) 
          ){
            posX = +target.left;
          }
          else {
            posX = window.pageXOffset + (dx * freq);
          }
    
          if(
            (dy <= 0 && window.pageYOffset <= +target.top) ||
            (dy >= 0 && window.pageYOffset >= +target.top) 
          ){
            posY = +target.top;
          }
          else {
            posY = window.pageYOffset + (dx * freq);
          }
          // move to the new position
          original.call(window, posX, posY);
          // while we are not ok on both axis
          if(posX !== +target.left || posY !== +target.top) {
            requestAnimationFrame(anim);
          }
          else {
            animating = false;
          }
        }
      }
    })();
    
    // OP's code,
    // by the time you click the button, the polyfill should already be set up if needed
    function scrollWin() {
      window.scrollTo({
        left: 1000,
        top: 1000,
        behavior: 'smooth'
      });
    }
    body {
      height: 5000px;
      width: 5000px;
    }
    <p>Click the button to scroll the document window to 1000 pixels.</p>
    <button onclick="scrollWin()">Click me to scroll!</button>

    0 讨论(0)
  • 2020-12-03 21:54

    You can try to use Element.ScrollLeft and Element.ScrollTop property with Window.scrollTo().

    Below is the example which is working with Edge and other browsers.

    <html>
    <style>
        body {
            height: 5000px;
            width: 5000px;
        }
    </style>
    <p>Click the button to scroll the document window to 1000 pixels.</p>
    <button onclick="scrollWin(this)">Click me to scroll!</button>
    <script>
        function scrollWin(pos) {
            window.scrollTo(pos.offsetTop+1000,pos.offsetLeft+1000);
                
          
        }
    </script>
    </html>

    Smooth behavior is not working with this code.

    Reference:

    Element.scrollLeft

    Element.scrollTop

    Regards

    Deepak

    0 讨论(0)
提交回复
热议问题