Dado que el DOM no ofrece ninguna forma de diferenciar entre el primer evento de desplazamiento disparado y los subsecuentes que pasan a ser parte del mismo movimiento de desplazamiento, estamos obligados a pensar en métodos indirectos para distinguirlos.
Si se desplaza rápidamente por cualquier elemento en particular, se desencadena un evento de desplazamiento muchas veces en secuencia. Usando el siguiente código, podemos tener una idea sobre exactamente la frecuencia con que ocurre:
$('#exampleDiv').bind('mousewheel', function() {
console.log(new Date().getTime());
});
Cuando se desplaza sobre ese div, usted obtendrá una salida de la consola que tiene este aspecto:
// Using mouse wheelbar
251327626600149
251327626600215
251327626600265
251327626600282
251327626600332
251327626600365
// Using touchpad
251327626626207
251327626626225
251327626626261
251327626626276
251327626626312
251327626626345
Al observar esta salida, parece que los eventos mousescroll
se disparan generalmente entre 20 ms y 60 ms el uno del otro. Para estar seguros, tomaremos el extremo superior para ser de 100 ms. Esto es muy informativo, porque podemos usarlo para distinguir entre eventos de desplazamiento que son parte de la misma acción y aquellos que probablemente sean distintos e iniciados deliberadamente por el usuario.
Lo que podría hacer aquí es crear una variable de "marca de tiempo" globalmente accesible, actualizándola cada vez que se active un evento mousescroll
, sea exitoso o no. Algo como esto:
var timeStamp = new Date().getTime();
$('#exampleDiv').bind('mousewheel', function (event) {
var timeNow = new Date().getTime();
// Need to prevent the default scrolling behavior
event.preventDefault();
// If the last mousescroll happened less that 100 ms ago, update
// timeStamp and do nothing
if (timeNow - timeStamp < 100) {
timeStamp = timeNow;
return;
} else {
timeStamp = timeNow;
scrollToSomeOtherDiv();
}
});
Esto ignora efectivamente todos mousescroll
eventos que se disparan después del evento inicial que todos ellos precedido, pero comienza a trabajar de nuevo después de que el usuario ha hecho una pausa de 100 ms.
Esto resolvería su problema, excepto si su función scrollToSomeOtherDiv()
involucró algún tipo de animación que consume mucho tiempo. Por supuesto, puede hacer una booleana global isAnimating
, y verifique si es verdadera cada vez que se activa un evento mousescroll
(asegurándose de convertirlo en falso en una devolución de llamada una vez que la animación haya terminado).
Eso funcionaría, excepto que podría proporcionar una experiencia desagradable para el usuario. Alguien que quiera desplazarse rápidamente por dos paneles probablemente no pause entre pergaminos, incluso después de ver comenzar la animación. El código de arriba verá todos sus eventos mousescroll
como parte del mismo movimiento de desplazamiento y continuará ignorándolos.
En ese caso, simplemente podría usar el tiempo de animación como su umbral. Establece un timeStamp una vez que se inicia la animación y luego ignora todos los eventos mousescroll
durante ese período de tiempo. He escrito un ejemplo aquí: http://jsfiddle.net/Sg8JQ/
El código en cuestión está aquí:
var lastAnimation = 0;
var animationTime = 1000; // in ms
var quietPeriod = 500; // in ms, time after animation to ignore mousescroll
function scrollThis(event, delta, deltaX, deltaY) {
var timeNow = new Date().getTime();
// change this to deltaX/deltaY depending on which
// scrolling dir you want to capture
deltaOfInterest = deltaY;
if (deltaOfInterest == 0) {
// Uncomment if you want to use deltaX
// event.preventDefault();
return;
}
// Cancel scroll if currently animating or within quiet period
if(timeNow - lastAnimation < quietPeriod + animationTime) {
event.preventDefault();
return;
}
if (deltaOfInterest < 0) {
if ($('.active').next('div').length) {
lastAnimation = timeNow;
$('.active').removeClass('active').next('div').addClass('active');
$('html,body').animate({
scrollTop: $('.active').offset().top }, animationTime);
}
} else {
if ($('.active').prev('div').length) {
lastAnimation = timeNow;
$('.active').removeClass('active').prev('div').addClass('active');
$('html,body').animate({
scrollTop: $('.active').offset().top }, animationTime);
}
}
}
// Note: mousewheel() is defined in the mousewheel plugin by Brandon Aaron
// You could do without it, but you'd need to adjust for firefox and webkit
// separately.
//
// You couldn't use $(document).scroll() because it doesn't allow you to
// preventDefault(), which I use here.
$(document).mousewheel(scrollThis);
También he incluido quietPeriod
que es el tiempo más allá del tiempo de animación durante el cual desea seguir ignorando mousescroll
eventos . Puede establecerlo en 0 si desea que el desplazamiento sea "receptivo" tan pronto como se complete la animación.
Cada solución que ideé implicaba establecer algunas En el proyecto en cuestión, los mods de diseño finalmente negaron el problema, pero todavía me ha afectado. Justo ayer tuve un problema en el mismo estadio, no similar, en el que necesitaba detener un anima te() en el tamaño de la ventana. Volví a mi mente sobre este tema. Creo que lo que ha proporcionado es la cosa más sexy/más probable que he encontrado hasta la fecha. Por eso, te señalo el ganador. – wlangley
¡Impresionante! Llamar un pedazo de código 'sexy' es tanto un cumplido como puedo manejar. Sin embargo, estaba pensando en tu idea de diseño inicial: con el desplazamiento horizontal, aún te toparían algunos problemas con los gestos en mac (ya que dos movimientos de dedo se desplazan y te llevan de regreso en la historia en Chrome y Safari). Todavía tengo que encontrar una solución para ese problema. – pikappa
Con valores inferiores a 500 (hablando de animationTime y quietPeriod), este patrón deja de funcionar. vuelve a intentar tu jsfiddle con valores bajos y verás el punto donde, en mi opinión, deja de funcionar como se esperaba. Solución apreciada si alguna – Ben