Detectar si el usuario creó el evento de desplazamiento

82

¿Es posible saber si un evento de desplazamiento fue realizado por el navegador o por el usuario? Específicamente, cuando se usa el botón Atrás, un navegador puede saltar a la última posición de desplazamiento conocida. Si me vinculo al evento de desplazamiento, ¿cómo puedo saber si esto fue causado por el usuario o el navegador?

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

Veo tres tipos de situaciones que provocan el desplazamiento en un navegador.

  1. El usuario realiza alguna acción. Por ejemplo, utiliza la rueda del mouse, las teclas de flecha, las teclas de avance / retroceso de página, las teclas de inicio / fin.
  2. El navegador se desplaza automáticamente. Por ejemplo, cuando utilice el botón Atrás en su navegador, saltará automáticamente a la última posición de desplazamiento conocida.
  3. Se desplaza Javascript. Por ejemplo element.scrollTo(x,y),.
mrtsherman
fuente
1
No estoy seguro de su pregunta, si considera que el salto usando el botón de retroceso para mí es un evento de desplazamiento del navegador o del usuario. Generalmente: ¿Qué consideras "desplazarse por el navegador"? Si te refieres al desplazamiento iniciado por tu script, entonces todo lo que necesitas hacer es cuando tu script se desplaza, ya sea para desactivar el controlador de eventos o establecer una bandera para que el controlador de eventos sepa ignorarlo.
RoToRa
Consideré que desplazarse mediante el botón Atrás era un "desplazamiento del navegador". Cualquier otra cosa: rueda del mouse, flechas arriba / abajo, clic en el botón central, etc. sería un desplazamiento del usuario. Supongo que mi pregunta real puede ser: ¿hay alguna forma de diferenciar de dónde vino un evento? Miré las propiedades del objeto de evento, pero no pude encontrar nada. Los tres escenarios que puedo imaginar son el desplazamiento iniciado por el navegador, el desplazamiento iniciado por javascript y el desplazamiento iniciado por el usuario. Espero que aclare las cosas.
mrtsherman
@mrtsherman Encontré algunos de estos mientras lograba el mismo resultado: stackoverflow.com/questions/2834667/…
AlphaMale

Respuestas:

30

Desafortunadamente, no hay una forma directa de decirlo.

Yo diría que si puedes rediseñar tu aplicación para que no dependa de este tipo de flujo, hazlo.

Si no es así, una solución alternativa en la que puedo pensar es realizar un seguimiento de los desplazamientos iniciados por el usuario y comprobarlo para ver si el desplazamiento fue activado por el navegador o por el usuario.

Aquí hay un ejemplo que reuní y que hace esto bastante bien (excepto para los navegadores con los que jQuery history tiene problemas).

Debe ejecutar esto localmente para poder probarlo completamente (jsFiddle / jsbin no se ajustan bien ya que iFrame el contenido).

Aquí están los casos de prueba que validé:

  • Carga de la página: userScrollesfalse
  • Desplácese con el mouse / teclado: se userScrollconviertetrue
  • Haga clic en el enlace para ir a la parte inferior de la página; se userScrollconvierte enfalse
  • Haga clic en Atrás / Adelante - se userScrollconvierte en 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>

Notas:

  • Esto no rastrea el desplazamiento cuando el usuario arrastra la barra de desplazamiento con el mouse. Esto se puede agregar con más código, que dejé como ejercicio para ti.
  • event.keyCodes puede variar según el sistema operativo, por lo que es posible que deba cambiarlo de forma adecuada.

¡Espero que esto ayude!

Mrchief
fuente
Gracias Mrchief. Creo que esta es la mejor respuesta a pesar de que no es lo que esperaba que fuera (¿Qué? ¡¿No hay event.throwerpropiedad ?!).
mrtsherman
No tiene que presionar el control cuando usa las teclas de inicio y final para desplazarse.
Curtis
DOMMouseScroll no funciona en la última versión de Chrome en Mac. Es mejor usar document.addEventListener ('mousewheel' en lugar de configurar onmousewheel
Curtis
8

Hasta donde yo sé, es imposible (sin ningún trabajo) saber cuándo el "usuario" ha emitido un evento de desplazamiento o por otros medios.

Puede intentar (como otros mencionaron) capturar eventos de rueda del mouse, y luego probablemente intentar capturar el evento de pulsación de tecla en cualquier tecla que pueda activar el desplazamiento (flechas, espacio, etc.) mientras verifica qué elemento está enfocado actualmente, ya que, por ejemplo, no puede desplazarse usando teclas de flecha mientras escribe en un campo de entrada. En general, sería un guión complejo y desordenado.

Dependiendo de la situación con la que esté lidiando, supongo que podría "revertir la lógica", y en lugar de detectar eventos de desplazamiento emitidos por el usuario, simplemente conéctese a cualquier desplazamiento realizado mediante programación y trate cualquier evento de desplazamiento no creado por su código como hecho por un usuario. Como dije, depende de la situación y de lo que intentas lograr.

WTK
fuente
Gracias WTK. Esta fue la mejor respuesta por un tiempo, pero desafortunadamente para usted, Mrchief expuso su respuesta con algunos ejemplos de código, así que le di la recompensa. ¡Si pudiera haberlo dividido, lo habría hecho!
mrtsherman
4
Está bien. No se trata de recompensas, sino de difundir y adquirir conocimiento :)
WTK
8

En lugar de intentar capturar todos los eventos del usuario, es mucho más fácil hacer lo contrario y manejar solo los eventos programáticos, e ignorarlos.

Por ejemplo, este tipo de código funcionaría:

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

Luego, cuando llame setScrollTop(), se ignorará el evento de desplazamiento, mientras que si el usuario se desplaza con el mouse, el teclado o cualquier otra forma, el evento será procesado.

Laurent
fuente
2
Esto no detecta / ignora los eventos de desplazamiento activados por el navegador.
mfluehr
He estado pensando en algo similar que debo agregar: esto no tiene en cuenta que el comportamiento de desplazamiento se puede configurar en 'suave'
Kamil Bęben
3

Sí, es 100% posible. Actualmente estoy usando esto en una aplicación donde IE no es un requisito, solo para el cliente. Cuando mi aplicación Backbone inicia una animación en la que se cambia el desplazamiento, el desplazamiento se produce pero no activa estas capturas de eventos. Esto se prueba en FF, Safari y Chrome más reciente.

$('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)) { 
  }
}
}); 

fuente
1
Gracias por la sugerencia. Sin embargo, esta es una solución binaria: detecta desplazamiento iniciado por javascript frente a desplazamiento no js. Realmente necesito una solución ternaria. (javascript, usuario, navegador).
mrtsherman
2

Intente usar los eventos Mousewheel y DOMMouseScroll en su lugar. Ver http://www.quirksmode.org/dom/events/scroll.html

Gerben
fuente
Gracias por esta sugerencia. Sin embargo, no creo que esto tenga lo que quiero. De hecho, necesito diferenciar entre el navegador y el desplazamiento del usuario. No solo apunte a la rueda del mouse.
mrtsherman
Creo que el evento de la rueda del mouse se activa antes del evento de desplazamiento. Por lo tanto, puede establecer var userscroll = true en la rueda del mouse y detectarlo en onscroll (y restablecerlo a falso).
Gerben
3
Sin embargo, existe una gran cantidad de eventos de desplazamiento iniciados por el usuario que no se cubrirían. Teclas de flecha, cambio de tamaño de ventana, etc. Es mucho más seguro decir que el remitente de este evento es "X" que decir que todo lo que está fuera de este evento debe ser "X". Tampoco me funciona independientemente, ya que quiero identificar un desplazamiento iniciado por el navegador, no por el usuario. Entonces, para hacer este caso de uso, tendría que rastrear todos los desplazamientos de la rueda del mouse y luego intentar deducir si el evento de desplazamiento posterior se basó en el usuario. Me temo que es impracticable.
mrtsherman
2

Puede comprobar la posición de desplazamiento en listo. Cuando activa el evento de desplazamiento, compruebe que la posición de desplazamiento sea diferente a la que tenía cuando se cargó la página. Por último, asegúrese de borrar el valor almacenado una vez que se desplaza por la página.

$(function () {
    var loadScrollTop = ($(document).scrollTop() > 0 ? $(document).scrollTop() : null);
    $(document).scroll(function (e) {
        if ( $(document).scrollTop() !== loadScrollTop) {
            // scroll code here!
        }
        loadScrollTop = null;
    });
});
Vaqueros oxidados
fuente
Gracias por la idea. Hay algunos escenarios que esto no cubre del todo para mí. Aunque es una buena idea y lo guardaré en mi bolsillo trasero si alguna vez lo necesito.
mrtsherman
Sencillo y en la mayoría de los casos eficaz.
Aaron
0

Con respecto a:

Específicamente, al usar el botón Atrás, un navegador puede saltar a la última posición de desplazamiento conocida.

Eso se activa muy pronto, después de que se renderiza la página. Puede retrasar la escucha del evento de desplazamiento en aproximadamente 1 segundo.

Primero - LetsWP.io
fuente
2
Ésta no sería una solución sólida. El tiempo para saltar depende del tiempo de carga de la página. El navegador demora el salto hasta que la página está completamente cargada. Esto evita que los elementos de la página que se cargan y cambian la altura de la página (como imágenes) se desplacen fuera de la vista.
mrtsherman
0

Hay una forma más de separar el desplazamiento creado por el usuario: puede utilizar los controladores de eventos alternativos, por ejemplo, 'mousewheel', 'touchmove', 'keydown' con los códigos 38 y 40 para desplazamiento de flechas, para desplazamiento con barra de desplazamiento, si El evento 'scroll' se dispara simultáneamente con el evento 'mousedown' hasta el evento 'mouseup'.

Alex Kuzmin
fuente
-1

Encontré esto muy útil. Aquí hay una versión de coffeescript para aquellos que así lo deseen.

$ ->
  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;
no tumba
fuente
-1

si está usando JQuery, entonces hay una mejor respuesta, aparentemente, solo lo estoy probando yo mismo.

ver: Detectar el desencadenador de eventos de jquery por usuario o llamar por código

nathan g
fuente
Gracias por la sugerencia. Sin embargo, esta es una solución binaria: detecta desplazamiento iniciado por javascript frente a desplazamiento no js. Realmente necesito una solución ternaria. (javascript, usuario, navegador). Nota al margen, jQuery no es un requisito para su solución propuesta.
mrtsherman
-1

Puede que no ayude con su aplicación, pero necesitaba disparar un evento en el desplazamiento del usuario, pero no en el desplazamiento programático y la publicación en caso de que ayude a alguien más.

Escuche el wheelevento en lugar de scroll,

Se activa cada vez que un usuario usa la rueda del mouse o la almohadilla de seguimiento (que creo que es la forma en que la mayoría de la gente se desplaza de todos modos) y no se activa cuando se desplaza programáticamente. Lo usé para diferenciar entre una acción de desplazamiento de usuario y funciones de desplazamiento.

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


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

Una advertencia es que wheelno se activa con el desplazamiento en el móvil, así que verifiqué si el dispositivo era móvil y usé funciones similares.

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

TimothyBuktu
fuente