Определить, было ли событие прокрутки создано пользователем

84

Можно ли определить, было ли событие прокрутки выполнено браузером или пользователем? В частности, при использовании кнопки «Назад» браузер может перейти к последней известной позиции прокрутки. Если я привяжусь к событию прокрутки, как я могу определить, было ли это вызвано пользователем или браузером?

$(document).scroll( function(){ 
    //who did this?!
});

Я вижу три типа ситуаций, вызывающих прокрутку в браузере.

  1. Пользователь выполняет какое-то действие. Например, используется колесо мыши, клавиши со стрелками, клавиши перехода на страницу вверх / вниз, клавиши возврата или окончания.
  2. Браузер прокручивает автоматически. Например, при использовании кнопки «Назад» в браузере происходит автоматический переход к последней известной позиции прокрутки.
  3. Прокрутка Javascript. Например, element.scrollTo(x,y).
mrtsherman
источник
1
Я не уверен в вашем вопросе, если вы считаете, что прыжок с помощью кнопки возврата ко мне является событием прокрутки браузера или пользователя. В общем: что вы считаете «прокруткой в ​​браузере»? Если вы имеете в виду прокрутку, инициированную вашим сценарием, то все, что вам нужно сделать, это когда ваш сценарий прокручивается, либо деактивировать обработчик событий, либо установить флаг, чтобы обработчик событий знал, что нужно его игнорировать.
RoToRa
Я считал прокрутку с помощью кнопки возврата "прокруткой браузера". Все остальное - колесо мыши, стрелки вверх / вниз, нажатие центральной кнопки и т. Д. - будет прокруткой пользователя. Я предполагаю, что мой настоящий вопрос может заключаться в том, есть ли способ определить, откуда произошло событие? Я просмотрел свойства объекта события, но ничего не нашел. Я могу представить себе три сценария: прокрутка, инициируемая браузером, прокрутка, инициируемая javascript, и прокрутка, инициируемая пользователем. Надеюсь, это проясняет ситуацию.
mrtsherman
@mrtsherman Я нашел некоторые из них, достигнув того же результата: stackoverflow.com/questions/2834667/…
AlphaMale

Ответы:

30

К сожалению, нет прямого способа сказать это.

Я бы сказал, что если вы можете изменить дизайн своего приложения, чтобы оно не зависело от этого типа потока, сделайте это.

Если нет, то я могу придумать обходной путь - отслеживать прокрутки, инициированные пользователем, и проверять это, чтобы увидеть, была ли прокрутка запущена браузером или пользователем.

Вот пример, который я собрал, который делает это довольно хорошо (за исключением браузеров, в которых история jQuery имеет проблемы с).

Вам нужно запустить это локально, чтобы иметь возможность полностью протестировать (jsFiddle / jsbin не подходят, поскольку они iFrame содержимого).

Вот проверенные мной тестовые примеры :

  • Загрузка страницы - userScrollестьfalse
  • Прокрутка с помощью мыши / клавиатуры - userScrollстановитсяtrue
  • Щелкните ссылку, чтобы перейти к нижней части страницы - userScrollстановитсяfalse
  • Нажимаем Назад / Вперед - userScrollстановится false;

<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="utf-8" /> 
    <script src="http://code.jquery.com/jquery-1.6.1.min.js"></script> 
    <script type="text/javascript" src="https://raw.github.com/tkyk/jquery-history-plugin/master/jquery.history.js"></script> 
</head> 
<body> 
    <span> hello there </span><br/> 
    <a href="#bottom"> click here to go down </a> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <a name="bottom"> just sitting </a> 
</body> 
<script type="text/javascript"> 

var userScroll = false;     

function mouseEvent(e) { 
    userScroll = true; 
} 


$(function() { 

    // reset flag on back/forward 
    $.history.init(function(hash){ 
        userScroll = false; 
    }); 

    $(document).keydown(function(e) { 
        if(e.which == 33        // page up 
           || e.which == 34     // page dn 
           || e.which == 32     // spacebar
           || e.which == 38     // up 
           || e.which == 40     // down 
           || (e.ctrlKey && e.which == 36)     // ctrl + home 
           || (e.ctrlKey && e.which == 35)     // ctrl + end 
          ) { 
            userScroll = true; 
        } 
    }); 

    // detect user scroll through mouse
    // Mozilla/Webkit 
    if(window.addEventListener) {
        document.addEventListener('DOMMouseScroll', mouseEvent, false); 
    }

    //for IE/OPERA etc 
    document.onmousewheel = mouseEvent; 


    // to reset flag when named anchors are clicked
    $('a[href*=#]').click(function() { 
        userScroll = false;
    }); 

      // detect browser/user scroll
    $(document).scroll( function(){  
        console.log('Scroll initiated by ' + (userScroll == true ? "user" : "browser"));
    });
}); 
</script> 
</html>

Ноты:

  • Это не отслеживает прокрутку, когда пользователь перетаскивает полосу прокрутки с помощью мыши. Это можно добавить с помощью еще одного кода, который я оставил вам в качестве упражнения.
  • event.keyCodes может отличаться в зависимости от ОС, поэтому вам, возможно, придется изменить это соответствующим образом.

Надеюсь это поможет!

Господин
источник
Спасибо, мистер Чиф. Я думаю, что это лучший ответ, хотя это не то, на что я надеялся (Что? Нет event.throwerсобственности ?!).
mrtsherman
Вам не нужно нажимать элемент управления при использовании клавиш «домой» и «конец» для прокрутки.
Curtis
DOMMouseScroll не работает в последней версии Chrome на Mac. Лучше всего использовать document.addEventListener ('mousewheel' вместо установки onmousewheel
Curtis
9

Вместо того, чтобы пытаться поймать все пользовательские события, гораздо проще сделать обратное и обрабатывать только программные события - и игнорировать их.

Например, такой код будет работать:

// Element that needs to be scrolled
var myElement = document.getElementById('my-container');

// Flag to tell if the change was programmatic or by the user
var ignoreNextScrollEvent = false;

function setScrollTop(scrollTop) {
    ignoreNextScrollEvent = true;
    myElement.scrollTop = scrollTop
}

myElement.addEventListener('scroll', function() {
    if (ignoreNextScrollEvent) {
        // Ignore this event because it was done programmatically
        ignoreNextScrollEvent = false;
        return;
    }

    // Process user-initiated event here
});

Затем, когда вы вызываете setScrollTop(), событие прокрутки будет проигнорировано, в то время как, если пользователь прокручивает с помощью мыши, клавиатуры или любым другим способом, событие будет обработано.

Лоран
источник
2
Это не обнаруживает / игнорирует события прокрутки, запускаемые браузером.
mfluehr
Я подумал о том, чтобы добавить что-то подобное - это не учитывает, что поведение прокрутки может быть установлено на «плавное»
Камил Бембен
8

Насколько я знаю, невозможно (без какой-либо работы) определить, было ли событие прокрутки инициировано «пользователем» или другими способами.

Вы можете попробовать (как упоминалось другими) поймать события колесика мыши, а затем, вероятно, попытаться поймать событие нажатия клавиши на любых клавишах, которые могут запускать прокрутку (стрелки, пробел и т. Д.), Проверяя, какой элемент в настоящее время сфокусирован, поскольку вы, например, не можете прокручивать, используя клавиши со стрелками при вводе в поле ввода. В общем, это был бы сложный и запутанный сценарий.

В зависимости от ситуации, с которой вы имеете дело, я мог бы предположить, что вы «вернете логику», и вместо того, чтобы обнаруживать выданные пользователем события прокрутки, просто подключитесь к любым прокруткам, созданным программно, и обработайте любые события прокрутки, не созданные вашим кодом, как сделанные пользователем. Как я уже сказал, это зависит от ситуации и от того, чего вы пытаетесь достичь.

WTK
источник
Спасибо, WTK. На какое-то время это был лучший ответ, но, к сожалению для вас, Mrchief изложил свой ответ с некоторыми примерами кода, поэтому я назначил ему награду. Если бы я мог разделить это, я бы сделал это!
mrtsherman
4
Ничего страшного. Дело не в щедрости, а в распространении и получении знаний :)
WTK
3

Да, это возможно на все 100%. В настоящее время я использую это в приложении, где IE не является обязательным - только для клиента. Когда мое приложение Backbone инициирует анимацию, в которой прокрутка изменяется - прокрутка происходит, но не запускает эти захваты событий. Это проверено в последней версии FF, Safari и Chrome.

$('html, body').bind('scroll mousedown wheel DOMMouseScroll mousewheel keyup', function(evt) {
  // detect only user initiated, not by an .animate though
    if (evt.type === 'DOMMouseScroll' || evt.type === 'keyup' || evt.type === 'mousewheel') {
    // up
    if (evt.originalEvent.detail < 0 || (evt.originalEvent.wheelDelta && evt.originalEvent.wheelDelta > 0)) { 
    // down.
    } else if (evt.originalEvent.detail > 0 || (evt.originalEvent.wheelDelta && evt.originalEvent.wheelDelta < 0)) { 
  }
}
}); 

источник
1
Спасибо за предложение. Однако это бинарное решение - обнаруживает прокрутку, инициированную javascript, по сравнению с прокруткой, отличной от js. Мне действительно нужно тройное решение. (javascript, пользователь, браузер).
mrtsherman
2

Попробуйте вместо этого использовать события Mousewheel и DOMMouseScroll. См. Http://www.quirksmode.org/dom/events/scroll.html.

Гербен
источник
Спасибо за это предложение. Однако я не думаю, что это то, что я хочу. Мне действительно нужно различать браузер и пользовательскую прокрутку. Не просто нацельтесь на колесо мыши.
mrtsherman
Я думаю, что событие mousewheel запускается до события onscroll. Таким образом, вы можете установить var userscroll = true на колесе мыши и обнаружить его в onscroll (и сбросить его на false).
Гербен
3
Однако существует большое количество событий прокрутки, инициируемых пользователем, которые не рассматриваются. Клавиши со стрелками, изменение размера окна и т. Д. Гораздо безопаснее сказать, что отправителем этого события является «X», чем говорить, что все за пределами этого события должно быть «X». Это также не работает для меня, поскольку я хочу идентифицировать прокрутку, инициированную браузером, а не пользователем. Итак, чтобы использовать этот вариант использования, мне пришлось бы отслеживать все прокрутки колесика мыши, а затем пытаться определить, было ли последующее событие прокрутки пользователем на основе этого. Боюсь, это просто невозможно.
mrtsherman
2

Вы можете проверить положение прокрутки, когда будете готовы. Когда вы запускаете событие при прокрутке, убедитесь, что положение прокрутки отличается от того, которое было при загрузке страницы. Наконец, не забудьте очистить сохраненное значение после прокрутки страницы.

$(function () {
    var loadScrollTop = ($(document).scrollTop() > 0 ? $(document).scrollTop() : null);
    $(document).scroll(function (e) {
        if ( $(document).scrollTop() !== loadScrollTop) {
            // scroll code here!
        }
        loadScrollTop = null;
    });
});
Ржавые джинсы
источник
Спасибо за идею. Есть некоторые сценарии, которые мне не совсем подходят. Хотя это хорошая идея, и я буду держать ее в заднем кармане, если она мне когда-нибудь понадобится.
mrtsherman
Просто и в большинстве случаев эффективно.
Aaron
0

Относительно:

В частности, при использовании кнопки «Назад» браузер может перейти к последней известной позиции прокрутки.

Это срабатывает очень скоро после рендеринга страницы. Вы можете просто отложить прослушивание события прокрутки примерно на 1 секунду.

Фирш - LetsWP.io
источник
2
Это не было бы надежным решением. Время перехода зависит от времени загрузки страницы. Браузер задерживает переход до тех пор, пока страница не загрузится полностью. Это предотвращает прокрутку элементов страницы, которые загружаются и изменяют высоту страницы (например, изображений), из поля зрения.
mrtsherman
0

Есть еще один способ отделить созданную пользователем прокрутку: вы можете использовать альтернативные обработчики событий, например, 'mousewheel', 'touchmove', 'keydown' с кодами 38 и 40 для прокрутки стрелок, для прокрутки с полосой прокрутки - если Событие 'scroll' запускается одновременно с 'mousedown' до события 'mouseup'.

Алексей Кузьмин
источник
-1

Нашел это очень полезным. Вот вариант кофе для тех, кто так склонен.

$ ->
  S.userScroll = false

  # reset flag on back/forward
  if $.history?
    $.history.init (hash) ->
      S.userScroll = false

  mouseEvent = (e)->
    S.userScroll = true

  $(document).keydown (e) ->
    importantKey =  (e.which == 33 or        # page up
      e.which == 34 or    # page dn
      e.which == 32 or    # spacebar
      e.which == 38 or    # up
      e.which == 40 or    # down
      (e.ctrlKey and e.which == 36) or    # ctrl + home
      (e.ctrlKey and e.which == 35)    # ctrl + end
    )
    if importantKey
      S.userScroll = true;

  # Detect user scroll through mouse
  # Mozilla/Webkit
  if window.addEventListener
      document.addEventListener('DOMMouseScroll', mouseEvent, false);

  # for IE/OPERA etc
  document.onmousewheel = mouseEvent;
не надгробие
источник
-1

Если вы используете JQuery, то, по-видимому, есть лучший ответ - я просто пробую сам.

см .: Обнаружение триггера события jquery пользователем или вызов по коду

Натан Г
источник
Спасибо за предложение. Однако это бинарное решение - обнаруживает прокрутку, инициированную javascript, по сравнению с прокруткой, отличной от js. Мне действительно нужно тройное решение. (javascript, пользователь, браузер). Замечание, jQuery не является обязательным требованием для вашего предлагаемого решения.
mrtsherman
-1

Это может не помочь вашему приложению, но мне нужно было запускать событие при прокрутке пользователя, но не программную прокрутку и публикацию, если это поможет кому-то еще.

Слушайте wheelсобытия вместо scroll,

Он запускается всякий раз, когда пользователь использует колесо мыши или трекер (что, как мне кажется, в любом случае прокручивает большинство людей), и не запускается, когда вы программно прокручиваете. Я использовал его, чтобы различать действие прокрутки пользователя и функции прокрутки.

https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event


element.addEventListener('wheel', (event) => {
       //do user scroll stuff here
})

Одно предостережение заключается в том, что wheelна мобильных устройствах не запускается прокрутка, поэтому я проверил, было ли устройство мобильным, и использовал ли он аналогичные функции.

if(this.mobile){
  element.addEventListener('scroll', (event) => {
       //do mobile scroll stuff here
  })
}

ТимотиБукту
источник