Wykryj, czy zdarzenie przewijania zostało utworzone przez użytkownika

84

Czy można stwierdzić, czy zdarzenie przewijania zostało wykonane przez przeglądarkę, czy przez użytkownika? W szczególności podczas korzystania z przycisku Wstecz przeglądarka może przejść do ostatniej znanej pozycji przewijania. Jeśli powiążę się ze zdarzeniem przewijania, jak mogę stwierdzić, czy jest to spowodowane przez użytkownika czy przeglądarkę?

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

Widzę trzy rodzaje sytuacji, które powodują przewijanie w przeglądarce.

  1. Użytkownik wykonuje jakąś czynność. Na przykład używa kółka myszy, klawiszy strzałek, klawiszy strona w górę / w dół, klawiszy home / end.
  2. Przeglądarka przewija się automatycznie. Na przykład, gdy użyjesz przycisku Wstecz w przeglądarce, automatycznie przeskoczy do ostatniej znanej pozycji przewijania.
  3. Przewija JavaScript. Na przykład element.scrollTo(x,y).
mrtsherman
źródło
1
Nie jestem pewien z twojego pytania, czy uważasz, że skok za pomocą przycisku wstecznego jest dla mnie zdarzeniem przewijania przeglądarki lub użytkownika. Ogólnie: co uważasz za „przewijanie przez przeglądarkę”? Jeśli masz na myśli przewijanie zainicjowane przez twój skrypt, to wszystko, co musisz zrobić, to kiedy skrypt się przewija, aby albo dezaktywować moduł obsługi zdarzeń, albo ustawić flagę, aby program obsługi zdarzeń wiedział, że ma to zignorować.
RoToRa
Uważam, że przewijanie za pomocą przycisku Wstecz jest „przewijaniem przeglądarki”. Cokolwiek innego - kółko myszy, strzałki w górę / w dół, kliknięcie środkowego przycisku itp. Byłoby przewijaniem użytkownika. Wydaje mi się, że moje prawdziwe pytanie może brzmieć - czy istnieje sposób, aby odróżnić, skąd się wzięło wydarzenie? Przyjrzałem się właściwościom obiektu zdarzenia, ale nie mogłem nic znaleźć. Trzy scenariusze, które mogę sobie wyobrazić, to przewijanie inicjowane przez przeglądarkę, przewijanie inicjowane przez JavaScript i przewijanie inicjowane przez użytkownika. Mam nadzieję, że to wszystko wyjaśnia.
mrtsherman
@mrtsherman Niektóre z nich znalazłem, osiągając te same wyniki: stackoverflow.com/questions/2834667/ ...
AlphaMale

Odpowiedzi:

30

Niestety nie ma bezpośredniego sposobu, aby to powiedzieć.

Powiedziałbym, że jeśli możesz przeprojektować swoją aplikację tak, aby nie zależała od tego typu przepływu, idź do tego.

Jeśli nie, rozwiązaniem, które przychodzi mi do głowy , jest śledzenie przewinięć inicjowanych przez użytkownika i sprawdzanie, czy przewijanie zostało uruchomione przez przeglądarkę, czy przez użytkownika.

Oto przykład, który złożyłem razem, który robi to całkiem dobrze (z wyjątkiem przeglądarek, w których historia jQuery ma problemy).

Musisz to uruchomić lokalnie, aby móc to w pełni przetestować (jsFiddle / jsbin nie są dobrze dopasowane, ponieważ umieszczają w ramkach zawartość).

Oto przypadki testowe, które zweryfikowałem:

  • Ładowanie strony - userScrolljestfalse
  • Przewiń za pomocą myszy / klawiatury - userScrollstaje siętrue
  • Kliknij link, aby przejść na dół strony - userScrollstaje sięfalse
  • Kliknij Wstecz / Dalej - userScrollstaje się 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>

Uwagi:

  • Nie śledzi przewijania, gdy użytkownik przeciąga pasek przewijania za pomocą myszy. Można to dodać za pomocą dodatkowego kodu, który zostawiłem dla Ciebie jako ćwiczenie.
  • event.keyCodes może się różnić w zależności od systemu operacyjnego, więc może być konieczna odpowiednia zmiana.

Mam nadzieję że to pomoże!

Mrchief
źródło
Dziękuję Mrchief. Myślę, że to najlepsza odpowiedź, mimo że nie jest taka, jak się spodziewałem (Co? Nie ma event.throwerwłasności ?!).
mrtsherman
Nie musisz naciskać klawisza Ctrl, gdy do przewijania używasz klawiszy domu i zakończenia.
Curtis
DOMMouseScroll nie działa w najnowszej wersji Chrome na Macu. Najlepiej używać document.addEventListener („kółko myszy” zamiast ustawiania kółka myszy
Curtis
9

Zamiast próbować wyłapać wszystkie zdarzenia użytkownika, znacznie łatwiej jest zrobić coś odwrotnego i obsłużyć tylko zdarzenia programistyczne - i je zignorować.

Na przykład taki kod zadziałaby:

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

Wtedy, gdy zadzwonisz setScrollTop(), zdarzenie scroll zostanie zignorowane, a jeśli użytkownik przewinie myszką, klawiaturą lub w jakikolwiek inny sposób, zdarzenie zostanie przetworzone.

laurent
źródło
2
To nie wykrywa / nie ignoruje zdarzeń przewijania wyzwalanych przez przeglądarkę.
mfluehr
Myślałem o czymś podobnym do dodania - to nie bierze pod uwagę, że zachowanie przewijania można ustawić
Kamil Bęben
8

O ile wiem, niemożliwe jest (bez żadnej pracy) stwierdzenie, kiedy zdarzenie przewijania zostało wywołane przez „użytkownika” lub w inny sposób.

Możesz spróbować (jak wspominali inni) złapać zdarzenia kółka myszy, a następnie prawdopodobnie spróbować złapać zdarzenie keydown na dowolnym klawiszu, który może wywołać przewijanie (strzałki, spacja itp.), Jednocześnie sprawdzając, który element jest aktualnie aktywny, ponieważ na przykład nie możesz przewijać za pomocą klawisze strzałek podczas pisania w polu wprowadzania. Ogólnie byłby to złożony i niechlujny skrypt.

W zależności od sytuacji, z którą masz do czynienia, możesz zgadnąć "odwrócić logikę" i zamiast wykrywać zdarzenia przewijania wydawane przez użytkownika po prostu podłącz się do wszelkich zwojów wykonanych programowo i traktuj wszystkie zdarzenia przewijania nie zostały wykonane przez kod, jako wykonane przez użytkownika. Jak powiedziałem, zależy to od sytuacji i tego, co próbujesz osiągnąć.

WTK
źródło
Dzięki WTK. To była najlepsza odpowiedź przez chwilę, ale niestety dla ciebie Mrchief objaśnił twoją odpowiedź z kilkoma przykładami kodu, więc oddałem mu nagrodę. Gdybym mógł to podzielić, zrobiłbym to!
mrtsherman
4
W porządku. Nie chodzi o bounty, ale o szerzenie i zdobywanie wiedzy :)
WTK
3

Tak, jest to w 100% możliwe. Obecnie używam tego w aplikacji, w której IE nie jest wymagane - tylko dla klienta. Kiedy moja aplikacja Backbone inicjuje animację, w której przewijanie jest zmieniane - przewijanie występuje, ale nie powoduje przechwytywania tych zdarzeń. Jest to testowane w najnowszych wersjach FF, Safari i 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)) { 
  }
}
}); 

źródło
1
Dzieki za sugestie. Jest to jednak rozwiązanie binarne - wykrywa przewijanie zainicjowane przez javascript i przewijanie inne niż js. Naprawdę potrzebuję trójskładnikowego rozwiązania. (javascript, użytkownik, przeglądarka).
mrtsherman
2

Zamiast tego spróbuj użyć zdarzeń Mousewheel i DOMMouseScroll. Zobacz http://www.quirksmode.org/dom/events/scroll.html

Gerben
źródło
Dziękuję za tę sugestię. Jednak nie sądzę, że to ma to, czego chcę. Właściwie muszę odróżnić przeglądarkę od przewijania użytkownika. Nie tylko celuj w kółko myszy.
mrtsherman
Myślę, że zdarzenie mousewheel jest uruchamiane przed zdarzeniem onscroll. Możesz więc ustawić var ​​userscroll = true na mousewheel i wykryć to w onscroll (i zresetować na false).
Gerben,
3
Istnieje wiele zdarzeń przewijania inicjowanych przez użytkownika, które nie zostałyby omówione. Klawisze strzałek, zmiana rozmiaru okna itp. Znacznie bezpieczniej jest powiedzieć, że nadawcą tego zdarzenia jest „X”, niż powiedzieć, że wszystko poza tym wydarzeniem musi mieć wartość „X”. To również nie działa dla mnie, niezależnie od tego, że chcę zidentyfikować przewijanie zainicjowane przez przeglądarkę, a nie przez użytkownika. Tak więc, aby zrobić ten przypadek użycia, musiałbym śledzić wszystkie przewijania kółkiem myszy, a następnie spróbować wywnioskować, czy kolejne zdarzenie przewijania było oparte na tym przez użytkownika. Obawiam się, że jest to po prostu niewykonalne.
mrtsherman
2

Możesz sprawdzić pozycję przewijania w gotowości. Po uruchomieniu zdarzenia przy przewijaniu sprawdź, czy pozycja przewijania jest inna niż w momencie wczytywania strony. Na koniec pamiętaj, aby wyczyścić przechowywaną wartość po przewinięciu strony.

$(function () {
    var loadScrollTop = ($(document).scrollTop() > 0 ? $(document).scrollTop() : null);
    $(document).scroll(function (e) {
        if ( $(document).scrollTop() !== loadScrollTop) {
            // scroll code here!
        }
        loadScrollTop = null;
    });
});
Rusty Jeans
źródło
Dziękuję za ten pomysł. Jest kilka scenariuszy, których mi to nie wystarcza. Chociaż to dobry pomysł i jeśli będę go potrzebował, trzymam go w tylnej kieszeni.
mrtsherman
Prosty iw większości przypadków skuteczny.
Aaron,
0

W odniesieniu do:

W szczególności podczas korzystania z przycisku Wstecz przeglądarka może przejść do ostatniej znanej pozycji przewijania.

Uruchamia się to bardzo szybko po wyrenderowaniu strony. Możesz po prostu opóźnić odsłuchiwanie zdarzenia przewijania o około 1 sekundę.

Firsh - LetsWP.io
źródło
2
Nie byłoby to solidne rozwiązanie. Czas przeskoczenia zależy od czasu wczytywania strony. Przeglądarka opóźnia przeskakiwanie do całkowitego załadowania strony. Zapobiega to przewijaniu się elementów strony, które ładują się i zmieniają wysokość strony (takich jak obrazy).
mrtsherman
0

Istnieje jeszcze jeden sposób na oddzielenie przewijania utworzonego przez użytkownika: możesz użyć alternatywnych programów obsługi zdarzeń, na przykład `` kółka myszy '', `` ruchu dotykowego '', `` klawisza w dół '' z kodami 38 i 40 do przewijania strzałkami, przewijania za pomocą paska przewijania - jeśli Zdarzenie „scroll” jest uruchamiane jednocześnie ze zdarzeniem „mousedown” aż do zdarzenia „mouseup”.

Alex Kuzmin
źródło
-1

Okazało się to bardzo przydatne. Oto wersja Coffeescript dla tych, którzy są tak skłonni.

$ ->
  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;
nottombrown
źródło
-1

jeśli używasz JQuery, to najwyraźniej istnieje lepsza odpowiedź - po prostu próbuję to zrobić.

zobacz: Wykrywanie wyzwalania zdarzenia jquery przez użytkownika lub wywołanie po kodzie

nathan g
źródło
Dzieki za sugestie. Jest to jednak rozwiązanie binarne - wykrywa przewijanie inicjowane przez javascript i przewijanie inne niż js. Naprawdę potrzebuję trójskładnikowego rozwiązania. (javascript, użytkownik, przeglądarka). Uwaga, jQuery nie jest wymaganiem dla twojego rozwiązania propsed.
mrtsherman
-1

To może nie pomóc w twojej aplikacji, ale musiałem uruchomić zdarzenie przy przewijaniu użytkownika, ale nie programowe przewijanie i publikowanie, jeśli pomoże to komukolwiek innemu.

Posłuchaj wheelzdarzenia zamiastscroll ,

Jest wyzwalany za każdym razem, gdy użytkownik używa kółka myszy lub trackera (co i tak wydaje mi się, że większość ludzi przewija) i nie jest uruchamiany, gdy przewijasz programowo. Użyłem go do rozróżnienia czynności przewijania użytkownika od funkcji przewijania.

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


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

Jedynym zastrzeżeniem jest to, że wheelnie uruchamia się przy przewijaniu na telefonie komórkowym, więc sprawdziłem, czy urządzenie jest mobilne i używa podobnych funkcji

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

TimothyBuktu
źródło