web-dev-qa-db-fra.com

Existe-t-il un moyen de détecter le défilement horizontal uniquement sans provoquer de refusion du navigateur?

Vous pouvez détecter un événement de défilement du navigateur sur un élément arbitraire avec:

element.addEventListener('scroll', function (event) {
    // do something
});

J'aimerais pouvoir faire la distinction entre le défilement vertical et le défilement horizontal et exécuter des actions pour eux indépendamment. 

Je le fais actuellement en stockant les valeurs de element.scrollTop et element.scrollLeft, puis en les comparant dans l'écouteur d'événements. Par exemple. 

var scrollLeft, scrollTop;
element.addEventListener('scroll', function (event) {
    if (scrollLeft !== element.scrollLeft) {
        // horizontally scrolled

        scrollLeft = element.scrollLeft;
    }

    if (scrollTop !== element.scrollTop) {
        // vertically scrolled

        scrollTop = element.scrollTop;
    }
});

Cela fonctionne bien, cependant, à partir de https://Gist.github.com/paulirish/5d52fb081b3570c81e3a J'ai lu que la lecture des valeurs scrollLeft ou scrollTop provoque un refoulement. 

Y a-t-il une meilleure façon de faire cela sans provoquer un reflow du navigateur sur le défilement?

16
magritte

Je pense que votre code est correct, car à la fin de la journée, vous devez lire l’une de ces propriétés pour connaître la direction de défilement, mais le élément clé pour éviter les problèmes de performances est de limiter le event, sinon l'événement scroll se déclenche trop souvent et constitue la cause première des problèmes de performances.

Ainsi, votre exemple de code adapté pour réguler l'événement scroll serait le suivant:

var ticking = false;
var lastScrollLeft = 0;
$(window).scroll(function() {
    if (!ticking) {
        window.requestAnimationFrame(function() {

            var documentScrollLeft = $(document).scrollLeft();
            if (lastScrollLeft != documentScrollLeft) {
                console.log('scroll x');
                lastScrollLeft = documentScrollLeft;
            }

            ticking = false;
        });
        ticking = true;
    }
});

Source: https://developer.mozilla.org/en-US/docs/Web/Events/scroll#Example

8
Nelson

Les éléments qui utilisent position: absolute; sont extraits du flux de documents (voir source) .

En gardant cela à l'esprit, vous pouvez utiliser CSS pour que votre element soit positionné de manière absolue dans son parent ...

#parent{
    width: 500px;
    height: 100px; 
    // ...or whatever dimensions you need the scroll area to be
    position: relative;
}
#element{
    position: absolute;
    top: 0px;
    left: 0px;
    bottom: 0px;
    right: 0px;
}

... et ensuite vous pouvez lire les attributs element.scrollLeft et element.scrollTop comme vous le faisiez dans votre question initiale sans crainte de goulets d'étranglement dus à la refusion de l'ensemble du DOM. 

Cette approche est également recommandée dans les consignes de développement de Google .

3
Marquizzo

Essayez la bibliothèque fast-dom:

Exécutez l'exemple de code de votre côté localement pour obtenir les données de profilage correctes.

import fastdom from 'fastdom'

let scrollLeft
let scrollTop
const fast = document.getElementById(`fast-dom`)

fast.addEventListener(`scroll`, function fastDom() {
  fastdom.measure(() => {
    if (scrollLeft !== fast.scrollLeft) {
      scrollLeft = fast.scrollLeft
      console.log(scrollLeft)
    }
    if (scrollTop !== fast.scrollTop) {
      scrollTop = fast.scrollTop
      console.log(scrollTop)
    }
  })
})

 enter image description here

 Edit fast-dom eaxmple

0
sultan

Qu'en est-il d'écouter l'entrée directement?

element.addEventListener('wheel', e => {
    if ( e.deltaX !== 0 ) {
        horizontal();
    }
    if ( e.deltaY !== 0 ) {
        vertical();
    }
})

Vous devrez peut-être aussi créer vos propres barres de défilement et écouter pour les déplacer. C'est réinventer la roue (lol) mais bon, il n'y a pas scrollTop ou scrollLeft en vue.

0
Ben West

Comme indiqué dans la réponse de @ Nelson, il n'y a aucun moyen de vérifier entre le défilement horizontal et le défilement vertical. La raison en est que l'objet événement déclenché ne contient aucune information (je viens de le vérifier).

Mozilla a de nombreux exemples sur la manière de limiter la vérification des propriétés de l'élément, mais je préfère personnellement l'approche setTimeout, car vous pouvez ajuster la réponse FPS en fonction de vos besoins. C'est dans cet esprit que j'ai écrit cet exemple de lecture-utilisation:

<html>
<head>
  <meta charset="utf-8"/>
  <style>
  #foo {
    display: inline-block;
    width: 500px;
    height: 500px;
    overflow: auto;
  }
  #baz {
    display: inline-block;
    background: yellow;
    width: 1000px;
    height: 1000px;
  }
  </style>
</head>
<body>
  <div id="foo">
    <div id="baz"></div>
  </div>
  <script>
    // Using ES5 syntax, with ES6 classes would be easier.
    function WaitedScroll(elem, framesPerSecond, onHorz, onVert) {
      this.isWaiting = false;
      this.prevLeft = 0;
      this.prevTop = 0;
      var _this = this;

      elem.addEventListener('scroll', function() {
        if (!_this.isWaiting) {
          _this.isWaiting = true;
          setTimeout(function() {
            _this.isWaiting = false;
            var curLeft = elem.scrollLeft;
            if (_this.prevLeft !== curLeft) {
              _this.prevLeft = curLeft;
              if (onHorz) onHorz();
            }
            var curTop = elem.scrollTop;
            if (_this.prevTop !== curTop) {
              _this.prevTop = curTop;
              if (onVert) onVert();
            }
          }, 1000 / framesPerSecond);
        }
      });
    }

    // Usage with your callbacks:
    var waiter = new WaitedScroll(
      document.getElementById('foo'), // target object to watch
      15, // frames per second response
      function() { // horizontal scroll callback
        console.log('horz');
      },
      function() { // vertical scroll callback
        console.log('veeeeeeeertical');
      }
    );
  </script>
</body>
</html>
0
Rodrigo

Vous pouvez utiliser API Intersection Observer

Il existe un w3c polyfill disponible depuis le support natif n'est pas assez répandu.

Le polyfill annule les événements de défilement

0
o.v.