¿Cómo evitar el desplazamiento de página al desplazar un elemento DIV?

94

He revisado y probado las diversas funciones para evitar que el cuerpo se desplace mientras está dentro de un div y he combinado una función que debería funcionar.

$('.scrollable').mouseenter(function() {
    $('body').bind('mousewheel DOMMouseScroll', function() {
        return false;
    });
    $(this).bind('mousewheel DOMMouseScroll', function() {
        return true;
    });
});
$('.scrollable').mouseleave(function() {
    $('body').bind('mousewheel DOMMouseScroll', function() {
        return true;
    });
});
  • Esto detiene todo el desplazamiento donde quiero que el desplazamiento aún sea posible dentro del contenedor
  • Esto tampoco se desactiva al dejar el mouse

¿Alguna idea o mejores formas de hacer esto?

RIK
fuente
u establece el cuerpo como retorno falso de la rueda del mouse, tal vez este es el problema, supongo que su contenedor está dentro del cuerpo, a la derecha
Ricardo Binns
@ric_bfa sí, pero cómo solucionarlo
RIK
en lugar del cuerpo, configure su contenedor de clase / identificación
Ricardo Binns
Quizás debería aclarar. Cuando el mouse está dentro del elemento '.scrollable', la capacidad del cuerpo para desplazarse debe desactivarse
RIK
4
¿No hay alguna manera de hacer esto con todo CSS y sin JavaScript?
chharvey

Respuestas:

165

Actualización 2: Mi solución se basa en deshabilitar el desplazamiento nativo del navegador por completo (cuando el cursor está dentro del DIV) y luego desplazar manualmente el DIV con JavaScript (configurando su .scrollToppropiedad). Un enfoque alternativo y mejor en mi opinión sería deshabilitar solo de forma selectiva el desplazamiento del navegador para evitar el desplazamiento de la página, pero no el desplazamiento DIV. Consulte la respuesta de Rudie a continuación, que demuestra esta solución.


Aqui tienes:

$( '.scrollable' ).on( 'mousewheel DOMMouseScroll', function ( e ) {
    var e0 = e.originalEvent,
        delta = e0.wheelDelta || -e0.detail;

    this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
    e.preventDefault();
});

Demostración en vivo: https://jsbin.com/howojuq/edit?js,output

Por lo tanto, configura manualmente la posición de desplazamiento y luego simplemente evita el comportamiento predeterminado (que sería desplazar el DIV o toda la página web).

Actualización 1: como Chris señaló en los comentarios a continuación, en las versiones más nuevas de jQuery, la información delta está anidada dentro del .originalEventobjeto, es decir, jQuery ya no la expone en su objeto de evento personalizado y tenemos que recuperarlo del objeto de evento nativo. .

Šime Vidas
fuente
2
@RobinKnight No, no lo parece. Aquí está la demostración de trabajo: jsfiddle.net/XNwbt/1 y aquí está la demostración con esas líneas eliminadas: jsfiddle.net/XNwbt/2
Šime Vidas
1
Su desplazamiento manual es hacia atrás en Firefox 10, al menos en Linux y Mac. Parece funcionar correctamente si lo hace -e.detail, probado en Firefox (Mac, Linux), Safari (Mac) y Chromium (Linux).
Anomie
1
@Anomie Eso es correcto. El signo de Mozilla .detailno se corresponde con el signo de .wheelData(que es lo que tienen Chrome / IE), por lo que tenemos que invertirlo manualmente. Gracias por arreglar mi respuesta.
Šime Vidas
8
Usé esto, ¡gracias! Necesitaba agregar esto aunque:if( e.originalEvent ) e = e.originalEvent;
Nikso
2
@ ŠimeVidas Hice un ajuste a esto después de implementarlo en mi software. No es crítico, pero posiblemente agregue una verificación de cordura para la propiedad de desplazamiento para evitar el desplazamiento del punto muerto cuando el elemento no tiene desplazamiento. if ( $(this)[0].scrollHeight !== $(this).outerHeight() ) { //yourcode }se ejecutará solo cuando haya una barra de desplazamiento.
user0000001
30

Si no le importa la compatibilidad con versiones anteriores de IE (<8), puede crear un complemento jQuery personalizado y luego llamarlo en el elemento desbordado.

Esta solución tiene una ventaja sobre la propuesta por Šime Vidas, ya que no sobrescribe el comportamiento de desplazamiento, solo la bloquea cuando es apropiado.

$.fn.isolatedScroll = function() {
    this.bind('mousewheel DOMMouseScroll', function (e) {
        var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail,
            bottomOverflow = this.scrollTop + $(this).outerHeight() - this.scrollHeight >= 0,
            topOverflow = this.scrollTop <= 0;

        if ((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
            e.preventDefault();
        }
    });
    return this;
};

$('.scrollable').isolatedScroll();
wojcikstefan
fuente
2
¡Gracias! Creo que es más apropiado no sobrescribir el comportamiento predeterminado. Para la compatibilidad con varios navegadores, se puede utilizar el complemento jQuery Mouse Wheel .
Meettya
Desafortunadamente, esto parece fallar en Chrome 52 (OSX 10.10). Sin embargo, funciona perfectamente en Safari.
helado
1
¡Gracias! Las otras soluciones hicieron que el desplazamiento fuera súper lento y con errores
agrublev
@wojcikstefan, recibo esta advertencia en Chrome:[Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' event. Consider marking event handler as 'passive' to make the page more responsive.
Syed
21

Creo que es posible cancelar el evento mousescroll a veces: http://jsfiddle.net/rudiedirkx/F8qSq/show/

$elem.on('wheel', function(e) {
    var d = e.originalEvent.deltaY,
        dir = d < 0 ? 'up' : 'down',
        stop = (dir == 'up' && this.scrollTop == 0) || 
               (dir == 'down' && this.scrollTop == this.scrollHeight-this.offsetHeight);
    stop && e.preventDefault();
});

Dentro del controlador de eventos, necesitará saber:

  • dirección de desplazamiento
    d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down' porque un número positivo significa desplazarse hacia abajo
  • posición de desplazamiento
    scrollToppara arriba, scrollHeight - scrollTop - offsetHeightpara abajo

Si eres

  • desplazarse hacia arriba y top = 0, o
  • desplazándose hacia abajo, y bottom = 0,

cancelar el evento: e.preventDefault()(y tal vez incluso e.stopPropagation()).

Creo que es mejor no anular el comportamiento de desplazamiento del navegador. Cancele solo cuando corresponda.

Probablemente no sea perfectamente xbrowser, pero no puede ser muy difícil. Quizás la dirección de desplazamiento dual de Mac sea complicada ...

Rudie
fuente
2
¿Cómo implemento la misma funcionalidad para dispositivos (tabletas / teléfonos)? Supongo que touchmove es el evento vinculante
ViVekH
6

Use la propiedad CSS debajo del overscroll-behavior: contain;elemento secundario

Prasoon Kumar
fuente
Todas las demás soluciones son exageradas, en comparación con esta regla CSS nativa. Bien hecho.
TechWisdom
3

mira si esto te ayuda:

demostración: jsfiddle

$('#notscroll').bind('mousewheel', function() {
     return false
});

editar:

prueba esto:

    $("body").delegate("div.scrollable","mouseover mouseout", function(e){
       if(e.type === "mouseover"){
           $('body').bind('mousewheel',function(){
               return false;
           });
       }else if(e.type === "mouseout"){
           $('body').bind('mousewheel',function(){
               return true;
           });
       }
    });
Ricardo Binns
fuente
Tus elementos están separados, mientras que uno de los míos está contenido en el otro.
RIK
3

Una solución menos hacky, en mi opinión, es establecer el desbordamiento oculto en el cuerpo cuando pasa el mouse sobre el div desplazable. Esto evitará que el cuerpo se desplace, pero se producirá un efecto de "salto" no deseado. La siguiente solución soluciona eso:

jQuery(".scrollable")
    .mouseenter(function(e) {
        // get body width now
        var body_width = jQuery("body").width();
        // set overflow hidden on body. this will prevent it scrolling
        jQuery("body").css("overflow", "hidden"); 
        // get new body width. no scrollbar now, so it will be bigger
        var new_body_width = jQuery("body").width();
        // set the difference between new width and old width as padding to prevent jumps                                     
        jQuery("body").css("padding-right", (new_body_width - body_width)+"px");
    })
    .mouseleave(function(e) {
        jQuery("body").css({
            overflow: "auto",
            padding-right: "0px"
        });
    })

Puede hacer que su código sea más inteligente si es necesario. Por ejemplo, puede probar si el cuerpo ya tiene un relleno y, en caso afirmativo, agregar el nuevo relleno a eso.

Alexandru Lighezan
fuente
3

En la solución anterior hay un pequeño error con respecto a Firefox. En Firefox, el evento "DOMMouseScroll" no tiene la propiedad e.detail, para obtener esta propiedad debe escribir lo siguiente 'e.originalEvent.detail'.

Aquí hay una solución funcional para Firefox:

$.fn.isolatedScroll = function() {
    this.on('mousewheel DOMMouseScroll', function (e) {
        var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.originalEvent.detail,
            bottomOverflow = (this.scrollTop + $(this).outerHeight() - this.scrollHeight) >= 0,
            topOverflow = this.scrollTop <= 0;

        if ((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
            e.preventDefault();
        }
    });
    return this;
};
Vladimir Kuzmin
fuente
Esto funciona bastante bien, excepto cuando chocas contra la parte superior o inferior del div que el primer evento lleva al cuerpo. Prueba con toque de 2 dedos en cromo en osx.
johnb003
3

aquí una solución simple sin jQuery que no destruye el desplazamiento nativo del navegador (esto es: sin desplazamiento artificial / feo):

var scrollable = document.querySelector('.scrollable');

scrollable.addEventListener('wheel', function(event) {
    var deltaY = event.deltaY;
    var contentHeight = scrollable.scrollHeight;
    var visibleHeight = scrollable.offsetHeight;
    var scrollTop = scrollable.scrollTop;

    if (scrollTop === 0 && deltaY < 0)
        event.preventDefault();
    else if (visibleHeight + scrollTop === contentHeight && deltaY > 0)
        event.preventDefault();
});

Demostración en vivo: http://jsfiddle.net/ibcaliax/bwmzfmq7/4/

Iñaki Baz Castillo
fuente
Acabo de publicar una biblioteca de NPM que implementa el código anterior: npmjs.com/package/dontscrollthebody
Iñaki Baz Castillo
2

Aquí está mi solución que he usado en aplicaciones.

Inhabilité el desbordamiento del cuerpo y coloqué todo el html del sitio web dentro de los div del contenedor. Los contenedores del sitio web se desbordan y, por lo tanto, el usuario puede desplazarse por la página como se esperaba.

Luego creé un div hermano (#Prevent) con un índice Z más alto que cubre todo el sitio web. Dado que #Prevent tiene un índice Z más alto, se superpone al contenedor del sitio web. Cuando #Prevent está visible, el mouse ya no se desplaza por los contenedores del sitio web, por lo que no es posible desplazarse.

Por supuesto, puede colocar otro div, como su modal, con un índice z más alto que #Prevent en el marcado. Esto le permite crear ventanas emergentes que no sufren problemas de desplazamiento.

Esta solución es mejor porque no oculta las barras de desplazamiento (efecto de salto). No requiere detectores de eventos y es fácil de implementar. Funciona en todos los navegadores, aunque con IE7 y 8 tienes que jugar (depende de tu código específico).

html

<body>
  <div id="YourModal" style="display:none;"></div>
  <div id="Prevent" style="display:none;"></div>
  <div id="WebsiteContainer">
     <div id="Website">
     website goes here...
     </div>
  </div>
</body>

css

body { overflow: hidden; }

#YourModal {
 z-index:200;
 /* modal styles here */
}

#Prevent {
 z-index:100;
 position:absolute;
 left:0px;
 height:100%;
 width:100%;
 background:transparent;
}

#WebsiteContainer {
  z-index:50;
  overflow:auto;
  position: absolute;
  height:100%;
  width:100%;
}
#Website {
  position:relative;
}

jquery / js

function PreventScroll(A) { 
  switch (A) {
    case 'on': $('#Prevent').show(); break;
    case 'off': $('#Prevent').hide(); break;
  }
}

deshabilitar / habilitar el desplazamiento

PreventScroll('on'); // prevent scrolling
PreventScroll('off'); // allow scrolling
David D
fuente
2
Considere incluir alguna información sobre su respuesta, en lugar de simplemente publicar el código. Tratamos de proporcionar no solo "soluciones", sino también ayudar a las personas a aprender. Debe explicar qué estaba mal en el código original, qué hizo de manera diferente y por qué funcionaron sus cambios.
Andrew Barber
1

Necesitaba agregar este evento a varios elementos que podrían tener una barra de desplazamiento. Para los casos en los que no había barra de desplazamiento, la barra de desplazamiento principal no funcionó como debería. Así que hice un pequeño cambio en el código @ Šime de la siguiente manera:

$( '.scrollable' ).on( 'mousewheel DOMMouseScroll', function ( e ) {
    if($(this).prop('scrollHeight') > $(this).height())
    {
        var e0 = e.originalEvent, delta = e0.wheelDelta || -e0.detail;

        this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
        e.preventDefault();
    }       
});

Ahora, solo los elementos con una barra de desplazamiento evitarán que el desplazamiento principal comience detenido.

Ricky
fuente
0

La versión javascript pura de la respuesta de Vidas, el$es el nodo DOM del plano en el que se está desplazando.

function onlyScrollElement(event, el$) {
    var delta = event.wheelDelta || -event.detail;
    el$.scrollTop += (delta < 0 ? 1 : -1) * 10;
    event.preventDefault();
}

¡Asegúrese de no adjuntar el par varias veces! Aquí hay un ejemplo,

var ul$ = document.getElementById('yo-list');
// IE9, Chrome, Safari, Opera
ul$.removeEventListener('mousewheel', onlyScrollElement);
ul$.addEventListener('mousewheel', onlyScrollElement);
// Firefox
ul$.removeEventListener('DOMMouseScroll', onlyScrollElement);
ul$.addEventListener('DOMMouseScroll', onlyScrollElement);

Una advertencia , la función debe ser una constante, si reinicializa la función cada vez antes de adjuntarla, es decir. var func = function (...)el removeEventListenerno funcionará.

srcspider
fuente
0

Puede hacer esto sin JavaScript. Puede establecer el estilo en ambos divs en position: fixedy overflow-y: auto. Es posible que deba hacer que uno de ellos sea más alto que el otro estableciendo suz-index (si se superponen).

Aquí hay un ejemplo básico de CodePen .

Carl Smith
fuente
0

Aquí está el complemento que es útil para evitar el desplazamiento de los padres mientras se desplaza un div específico y tiene un montón de opciones para jugar.

Compruébalo aquí:

https://github.com/MohammadYounes/jquery-scrollLock

Uso

Activar bloqueo de desplazamiento a través de JavaScript:

$('#target').scrollLock();

Activar bloqueo de desplazamiento mediante marcado:

    <!-- HTML -->
<div data-scrollLock
     data-strict='true'
     data-selector='.child'
     data-animation='{"top":"top locked","bottom":"bottom locked"}'
     data-keyboard='{"tabindex":0}'
     data-unblock='.inner'>
     ...
</div>

<!-- JavaScript -->
<script type="text/javascript">
  $('[data-scrollLock]').scrollLock()
</script>

Ver demostración

MR_AMDEV
fuente
0

simplemente ofreciendo esto como una posible solución si no cree que el usuario tendrá una experiencia negativa con el cambio obvio. Simplemente cambié la clase de desbordamiento del cuerpo a oculto cuando el mouse estaba sobre el div de destino; luego cambié el div del cuerpo a desbordamiento oculto cuando el mouse se va.

Personalmente, no creo que se vea mal, mi código podría usar alternar para hacerlo más limpio, y hay beneficios obvios para hacer posible este efecto sin que el usuario se dé cuenta. Entonces, esta es probablemente la respuesta hackish-last-resort.

//listen mouse on and mouse off for the button
pxMenu.addEventListener("mouseover", toggleA1);
pxOptContainer.addEventListener("mouseout", toggleA2);
//show / hide the pixel option menu
function toggleA1(){
  pxOptContainer.style.display = "flex";
  body.style.overflow = "hidden";
}
function toggleA2(){
  pxOptContainer.style.display = "none";
  body.style.overflow = "hidden scroll";
}
Thuperman
fuente