web-dev-qa-db-fra.com

window.scrollTo avec les options ne fonctionnant pas sur Microsoft Edge

J'ai un problème étrange que je ne peux répliquer que sur les navigateurs Microsoft (Edge et IE11 testés).

<style>
    body {
        height: 5000px;
        width: 5000px;
    }
</style>
<p>Click the button to scroll the document window to 1000 pixels.</p>
<button onclick="scrollWin()">Click me to scroll!</button>
<script>
    function scrollWin() {
        window.scrollTo({
            left: 1000, 
            top: 1000,
            behavior:"smooth"
        });
    }
</script>

Ce code fait défiler correctement la fenêtre de 1000 pixels vers la gauche et le bas, avec un comportement fluide dans Chrome et Firefox. Cependant, sur Edge et IE, il ne bouge pas du tout.

9
CDK

Ce n'est peut-être pas une vraie réponse au sens de la Parole, mais j'ai résolu ce problème en utilisant ce polyfill utile: https://github.com/iamdustan/smoothscroll qui fonctionne vraiment bien sur tous les navigateurs.

Exemple de page pour pollyfill: http://iamdustan.com/smoothscroll/

Un grand merci à l'auteur.

10
CDK

Comme mentionné précédemment, la spécification du comportement de défilement n'a été implémentée que dans Chrome, Firefox et Opera.

Voici une ligne pour détecter la prise en charge de la propriété behavior dans ScrollOptions:

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

Et voici une implémentation simple pour un défilement fluide entre les navigateurs: https://nicegist.github.io/d210786daa23fd57db59634dd231f341

11
eyecatchUp

Vous pouvez détecter la prise en charge de l'option behavior dans scrollTo à l'aide de cet extrait:

function testSupportsSmoothScroll () {
  var supports = false
  try {
    var div = document.createElement('div')
    div.scrollTo({
      top: 0,
      get behavior () {
        supports = true
        return 'smooth'
      }
    })
  } catch (err) {}
  return supports
}

Testé dans Chrome, Firefox, Safari et Edge, et semble fonctionner correctement. Si supports est faux, vous revenez à un polyfill.

3
nlawson

En effet, ils ne supportent pas cette variante, les articles MDN devraient être mis à jour.

Une façon de remplir cette méthode consiste à exécuter la méthode scroll dans une boucle alimentée requestAnimationFrame. Rien d'extraordinaire ici.

Le principal problème qui se pose est comment détecter quand cette variante n'est pas prise en charge. en fait @ réponse de nlawson résout parfaitement ce problème ...

Pour cela, nous pouvons utiliser le fait qu'un appel à Window # scroll déclenchera un ScrollEvent si le viewPort a effectivement défilé.
Cela signifie que nous pouvons configurer un test asynchrone qui:

  1. Attachez un gestionnaire d'événements au ScrollEvent,
  2. Appelez une première fois la variante scroll(left , top) pour être sûr que le Event se déclenchera,
  3. Remplacez cet appel par un second en utilisant la variante options.
  4. Dans le gestionnaire d'événements, si nous ne sommes pas à la position de défilement correcte, cela signifie que nous devons attacher notre polyfill.

La mise en garde de ce test est donc qu'il s'agit d'un test asynchrone. Mais comme vous devez réellement attendre que le document soit chargé avant d'appeler cette méthode, je suppose que dans 99% des cas, ce sera correct.

Maintenant, pour alléger le document principal, et puisqu'il s'agit déjà d'un test asynchrone, nous pouvons même envelopper ce test dans un iframe, ce qui nous donne quelque chose comme:

/* 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;
      }
    }
  }
})();


Désolé de ne pas avoir fourni de démonstration exécutable directement dans la réponse, mais les iframes sur-protégés de StackSnippet® ne nous permettent pas d'accéder au contenu d'une iframe interne sur IE ...
Donc, à la place, voici un lien vers un jsfiddle .


Post-scriptum: Je pense maintenant qu'il pourrait être possible de vérifier le support de manière synchrone en vérifiant le CSS scroll-behavior support, mais je ne suis pas sûr qu'il couvre vraiment tous les UA de l'histoire ...


Post-Post-scriptum: En utilisant la détection de @ nlawson, nous pouvons maintenant avoir un extrait de travail ;-)

/* 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>
2
Kaiido

Malheureusement, il n'y a aucun moyen pour cette méthode de fonctionner dans ces deux navigateurs. Vous pouvez vérifier les problèmes ouverts ici et voir qu'ils n'ont rien fait sur ce problème. https://developer.Microsoft.com/en-us/Microsoft-Edge/platform/issues/15534521/

0
Lazar Nikolic

Vous pouvez essayer d'utiliser les propriétés Element.ScrollLeft et Element.ScrollTop avec Window.scrollTo ().

Voici l'exemple qui fonctionne avec Edge et d'autres navigateurs.

<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>

Le comportement fluide ne fonctionne pas avec ce code.

Référence:

Element.scrollLeft

Element.scrollTop

Cordialement

Deepak

0
Deepak-MSFT