有没有办法只检测水平滚动而不触发浏览器回流

Is there a way to detect horizontal scroll only without triggering a browser reflow

您可以检测任意元素上的浏览器滚动事件:

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 值会导致回流。

有没有更好的方法来做到这一点而不会导致浏览器在滚动时回流?

我预计实际上没有...

但是根据How to detect horizontal scrolling in jQuery?你可以试试:

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

需要time-it仔细检查哪种方法最快...

我认为你的代码是正确的,因为在一天结束时你需要阅读其中一个属性来找出滚动方向,但是这里的关键避免性能问题的方法是 限制事件 ,否则 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

尝试fast-dom图书馆:

运行 the example code 在您的本地获取正确的分析数据。

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

使用 position: absolute; 的元素从文档流中删除 (see source)

考虑到这一点,您可以使用 CSS 让您的 element absolutely-positioned 在其父项中...

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

...然后您可以像在原始问题中那样随意阅读 element.scrollLeftelement.scrollTop 属性,而不必担心回流整个 DOM 造成瓶颈。

这种做法也是recommended in Google's developer guidelines

如@Nelson 的回答所述,无法检查水平和垂直滚动,原因是触发事件对象的相关信息为零(我刚检查过)。

Mozilla 有 a bunch of examples 如何限制元素 属性 检查,但我个人更喜欢 setTimeout 方法,因为你可以 fine-tune 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>

直接听输入怎么样?

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

您可能还需要创建自己的滚动条并监听拖动它们的情况。它正在重新发明轮子(笑),但是嘿,看不到 scrollTopscrollLeft

您可以使用Intersection Observer API

w3c polyfill available since native support 还不够广泛。

polyfill 去抖滚动事件

我一直在投资,我想出了这个解决方案:

水平滚动时,我不得不将 css 属性 更改为两个元素。希望它对任何想要控制这方面的人有所帮助。干杯!

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

下面我只给了横向滚动事件

const target = document.querySelector('.target');

function eventWheelHorizontal(e) {
  // Horizontal
  if (e.deltaX != '-0') {
    e.preventDefault();
    // working
  }
}

target.addEventListener('wheel', eventWheelHorizontal, { passive: false });

您可以通过应用这个简单的方法来分享活动。

const target = document.querySelector('.target');

function eventWheel(e) {
  // horizontal
  if (e.deltaX != '-0') {
    e.preventDefault();
    // working
  }
  // Vertical
  if (e.deltaY != '-0') {
    e.preventDefault();
    // working
  }
}

target.addEventListener('wheel', eventWheel, { passive: false });