【问题标题】:Is there a way to detect horizontal scroll only without triggering a browser reflow有没有办法只检测水平滚动而不触发浏览器重排
【发布时间】:2017-06-26 00:20:14
【问题描述】:

您可以通过以下方式检测任意元素上的浏览器滚动事件:

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

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

【问题讨论】:

  • 跟踪您元素的scrollLeftscrollTop 值,并查看它们在您处理滚动事件时是否发生变化,并在处理结束时更新它们,以便为下一次事件处理做好准备?
  • 如果你没有改变你的事件之间的DOM结构,它不会触发回流,或者更确切地说,回流将无关紧要。尼尔森是对的,无论如何你都应该限制滚动事件,但这不是因为回流,而仅仅是因为对这个“graphical”事件做出比屏幕刷新率更快的反应是没有意义的。

标签: javascript html reflow


【解决方案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

【讨论】:

  • 是的,最好使用 rAF 来限制此事件,如果您不想 preventDefault 事件,使用被动事件可能会更好。
【解决方案2】:

使用position: absolute; 的元素会从文档流(see source) 中取出。

考虑到这一点,您可以使用 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;
}

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

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

【讨论】:

  • 非常好的答案,谢谢...这个简单的技巧可以在使用 getBoundingClientRect 查找顶部/左侧属性时大大提高性能
【解决方案3】:

直接听输入怎么样?

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

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

【讨论】:

  • 这应该是只接受鼠标滚轮的答案。由于 DOM 发出的事件不同,因此水平拖动滚动条不会到达此代码。
  • 如果我们只是将 'wheel' 替换为 'scroll',这是完美的答案,因为对于 Wheel,它不会监听触摸滚动和鼠标拖动滚动条
  • 滚动事件中没有 deltaX 或 deltaY
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    您可以使用Intersection Observer API

    有一个w3c polyfill 可用,因为native support 不够广泛。

    polyfill 消除滚动事件的抖动

    【讨论】:

      【解决方案6】:

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

      Mozilla 有 a bunch of examples 说明如何限制元素属性检查,但我个人更喜欢 setTimeout 方法,因为您可以微调 FPS 响应以满足您的需求。考虑到这一点,我编写了这个易于阅读的示例:

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

      【讨论】:

        【解决方案7】:

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

        水平滚动时,我必须将一个 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;
            }
            })
            })
        

        【讨论】:

          【解决方案8】:

          我希望实际上没有......

          但是根据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;
              }
          });
          

          需要计时-仔细检查哪种方法最快...

          【讨论】:

            猜你喜欢
            • 2011-08-11
            • 2010-11-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-23
            • 2014-08-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多