Визначте, чи створив користувач подію прокрутки


84

Чи можна сказати, чи відбулася подія прокрутки браузером чи користувачем? Зокрема, під час використання кнопки "Назад" браузер може перейти до останньої відомої позиції прокрутки. Якщо я прив'язую подію прокрутки, як я можу зрозуміти, чи це було спричинено користувачем або браузером?

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

Я бачу три типи ситуацій, які викликають прокрутку в браузері.

  1. Користувач виконує певні дії. Наприклад, використовує коліщатко миші, клавіші зі стрілками, клавіші сторінки вгору / вниз, клавіші додому / кінця.
  2. Браузер прокручується автоматично. Наприклад, при використанні кнопки "Назад" у вашому браузері вона автоматично перейде до останньої відомої позиції прокрутки.
  3. Прокрутки Javascript. Наприклад, element.scrollTo(x,y).

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 може відрізнятися залежно від ОС, тому вам, можливо, доведеться змінити це відповідно.

Сподіваюся, це допомагає!


Дякую, Mrchief. Я думаю, що це найкраща відповідь, хоча це не те, на що я сподівався, що це буде (Що? Тут немає event.throwerвласності ?!).
mrtsherman

Вам не потрібно натискати клавішу управління під час використання клавіш "додому" та "закінчення" для прокрутки.
Кертіс,

DOMMouseScroll не працює на найновішій версії Chrome на Mac. Найкраще використовувати document.addEventListener ('коліщатко миші' замість того, щоб налаштовувати колесо миші
Кертіс,

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. Це була найкраща відповідь на деякий час, але, на жаль для вас, 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

Я думаю, що подія колеса миші запускається до події 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

Простий і в більшості випадків ефективний.
Аарон

0

Стосовно:

Зокрема, під час використання кнопки "Назад" браузер може перейти до останньої відомої позиції прокрутки.

Це спрацьовує дуже скоро, після відтворення сторінки. Ви можете просто затримати прослуховування події прокрутки приблизно на 1 секунду.


2
Це не було б надійним рішенням. Час стрибка залежить від часу завантаження сторінки. Браузер затримує перехід до повного завантаження сторінки. Це запобігає прокрутці елементів сторінки, які завантажуються та змінюють висоту сторінки (наприклад, зображення).
mrtsherman

0

Існує ще один спосіб відокремити створену користувачем прокрутку: ви можете використовувати альтернативні обробники подій, наприклад „коліщатко миші”, „переміщення дотику”, „клавіатура” з кодами 38 та 40 для прокрутки стрілки, для прокрутки за допомогою смуги прокрутки - якщо Подія "scroll" запускається одночасно з "mousedown" до події "mouseup".


-1

Знайшов це дуже корисним. Ось версія coffeescript для тих, хто так схильний.

$ ->
  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, користувач, браузер). Sidenote, 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
  })
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.