web-dev-qa-db-ja.com

ブラウザのリフローをトリガーせずに水平スクロールのみを検出する方法はありますか

次のようにして、任意の要素のブラウザスクロールイベントを検出できます。

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

縦スクロールと横スクロールを区別して、それぞれ独立してアクションを実行したい。

Element.scrollTopとelement.scrollLeftの値をスタッシュし、イベントリスナー内でそれらを比較することで、現在これを行っています。例えば。

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;
    }
});

これは正常に機能しますが、 https://Gist.github.com/paulirish/5d52fb081b3570c81e3a から、scrollLeftまたはscrollTopの値を読み取るとリフローが発生することを確認しました。

スクロール時にブラウザをリフローさせずにこれを行うより良い方法はありますか?

18
magritte

私のコードは正しいと思います。1日の終わりに、スクロール方向を見つけるためにこれらのプロパティの1つを読み取る必要がありますが、パフォーマンスの問題を回避するためにここで重要なことイベントをスロットルするです。そうしないと、scrollイベントが頻繁に発生し、それがパフォーマンスの問題の根本的な原因です。

したがって、scrollイベントを抑制するように調整されたサンプルコードは次のようになります。

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;
    }
});

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

10
Nelson

position: absolute;を使用する要素は、ドキュメントフローから除外されます (ソースを参照)

これを念頭に置いて、CSSを使用してelementをその親内に絶対配置することができます...

#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;
}

...そして、DOM全体のリフローによるボトルネックを心配することなく、元の質問と同じようにelement.scrollLeftおよびelement.scrollTop属性を自由に読むことができます。

このアプローチも Googleの開発者ガイドラインで推奨 です。

5
Marquizzo

fast-domライブラリを試してください:

ローカルで サンプルコード をローカルで実行して、正しいプロファイリングデータを取得します。

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

入力を直接聞くのはどうですか?

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

また、独自のスクロールバーを作成し、それらのドラッグを待機する必要がある場合もあります。車輪を再発明していますが(笑)、まあscrollTopscrollLeftは見えません。

0
Ben West

@Nelsonの回答で述べたように、水平スクロールと垂直スクロールの間をチェックする方法はありません、そしてその理由は、トリガーされたイベントオブジェクトがゼロに関する情報を持っているためですそれをチェックしました。

Mozillaには、要素のプロパティチェックを抑制する方法の 一連の例 がありますが、私は個人的にsetTimeoutアプローチを好みます。ニーズに合わせてFPS応答を微調整できるからです。そのことを念頭に置いて、私は次のRead-to-Useの例を書きました。

<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

Intersection Observer API を使用できます

ネイティブサポート が十分に普及していないため、 w3c polyfill を使用できます。

ポリフィルはスクロールイベントをデバウンスします

0
o.v.

私は投資してきましたが、このソリューションを思いつきました:

水平方向にスクロールするとき、cssプロパティを2つの要素に変更する必要がありました。それがこの側面を制御したい人に役立つことを願っています。乾杯!

var lastScrollLeft= 0;
$(window).scroll(function(){
    var position= $(document).scrollLeft();
    window.requestAnimationFrame(function() {
    if(position == 0){
            $("#top_box, #helper").css("position","fixed");
    }else if(position !== lastScrollLeft){
            $("#top_box, #helper").css("position","absolute");
            lastScrollLeft= position;
    }
    })
    })