Есть ли способ обнаружить горизонтальную прокрутку только без запуска переформатирования браузера

Вы можете обнаружить событие прокрутки браузера для произвольного элемента с помощью:

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 вызывает перекомпоновку.

Есть ли лучший способ сделать это, не вызывая перекомпоновки браузера при прокрутке?


person magritte    schedule 08.02.2017    source источник
comment
Отслеживайте значения scrollLeft и scrollTop вашего элемента и смотрите, изменились ли они при обработке события прокрутки, обновляя их в конце обработки, чтобы они были готовы к обработке следующего события?   -  person Mike 'Pomax' Kamermans    schedule 27.03.2018
comment
Если вы не меняли структуру DOM между вашими событиями, это не вызовет перекомпоновку, точнее, перекомпоновка не будет иметь никакого отношения. Нельсон прав, вам в любом случае следует ограничивать события прокрутки, но это не из-за перекомпоновки, а просто потому, что нет особого смысла реагировать быстрее, чем частота обновления экрана, на это графическое событие.   -  person Kaiido    schedule 27.03.2018


Ответы (8)


Я думаю, что ваш код правильный, потому что в конце концов вам нужно прочитать одно из этих свойств, чтобы узнать направление прокрутки, но ключевой момент здесь, чтобы избежать проблем с производительностью, — это ограничить событие, потому что в противном случае событие 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

person Nelson    schedule 22.03.2018
comment
Да, хорошо использовать rAF для регулирования этого события, может быть даже лучше использовать пассивные события, если вы не хотите preventDefault событие. - person Kaiido; 27.03.2018


А как насчет прослушивания ввода напрямую?

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

Вам также может понадобиться создать свои собственные полосы прокрутки и прослушивать их перетаскивание. Это изобретает велосипед (смеется), но scrollTop или scrollLeft не видно.

person Ben West    schedule 28.03.2018
comment
Это должен быть принятый ответ только для колесика мыши. Перетаскивание полосы прокрутки по горизонтали не приводит к этому коду, так как событие, генерируемое DOM, отличается. - person Culda-Daisa Andrei; 11.05.2021
comment
Это идеальный ответ, если мы просто заменим «колесо» на «прокрутку», потому что для колеса оно не будет прослушивать сенсорную прокрутку и перетаскивание полосы прокрутки мышью. - person Afzal Ali; 01.07.2021
comment
В событии прокрутки нет deltaX или deltaY. - person Ben West; 02.07.2021

Попробуйте библиотеку 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)
    }
  })
})

введите здесь описание изображения

  Изменить пример быстрого доступа

person sultan    schedule 26.03.2018

Вы можете использовать Intersection Observer API

Доступен полифилл w3c, поскольку встроенная поддержка недостаточно распространена.

Polyfill устраняет дребезг событий прокрутки

person Oleg    schedule 28.03.2018

Как сказано в ответе @Nelson, невозможно проверить между горизонтальной и вертикальной прокруткой, и причина в том, что объект инициированного события не имеет о нем нулевой информации (я только что проверил).

В Mozilla есть множество примеров того, как регулировать проверка свойства элемента, но я лично предпочитаю подход 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>
person rodrigocfd    schedule 27.03.2018

Я инвестировал, и я придумал это решение:

Мне пришлось изменить свойство 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;
    }
    })
    })
person Xabier Aparicio Muñoa    schedule 21.06.2019

Я ожидаю, что на самом деле нет...

Но в соответствии с Как обнаружить горизонтальную прокрутку в jQuery? вы можете попробовать :

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

Нужно время, чтобы перепроверить, какой метод самый быстрый ...

person A STEFANI    schedule 22.03.2018