Como evitar a rolagem da página ao rolar um elemento DIV?

94

Eu revisei e testei as várias funções para prevenir a habilidade do corpo de rolar enquanto dentro de uma div e combinei uma função que deve 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;
    });
});
  • Isso está interrompendo toda a rolagem onde eu quero que a rolagem ainda seja possível dentro do contêiner
  • Isso também não é desativado ao deixar o mouse

Alguma ideia ou melhor maneira de fazer isso?

RIK
fonte
vc definiu o corpo como retorno falso da roda do mouse, talvez este seja o problema, suponho que seu contêiner está dentro do corpo certo
Ricardo Binns
@ric_bfa sim, mas como consertar
RIK
em vez do corpo, defina seu contêiner de classe / id
Ricardo Binns
Talvez eu deva esclarecer. Quando o mouse está dentro do elemento '.scrollable', a capacidade do corpo de rolar deve ser desativada
RIK
4
Não existe uma maneira de fazer isso com todo o CSS e sem JavaScript?
chharvey,

Respostas:

165

Atualização 2: Minha solução é baseada em desabilitar a rolagem nativa do navegador completamente (quando o cursor está dentro do DIV) e então rolando manualmente o DIV com JavaScript (definindo sua .scrollToppropriedade). Uma alternativa e melhor abordagem IMO seria apenas desativar seletivamente a rolagem do navegador para evitar a rolagem da página, mas não a rolagem DIV. Confira a resposta de Rudie a seguir, que demonstra essa solução.


Aqui está:

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

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

Demonstração ao vivo: https://jsbin.com/howojuq/edit?js,output

Assim, você configura manualmente a posição de rolagem e, em seguida, evita o comportamento padrão (que seria rolar o DIV ou a página da web inteira).

Atualização 1: Como Chris observou nos comentários abaixo, nas versões mais recentes do jQuery, as informações delta estão aninhadas no .originalEventobjeto, ou seja, o jQuery não as expõe mais em seu objeto Event personalizado e, em vez disso, temos que recuperá-las do objeto Event nativo .

Šime Vidas
fonte
2
@RobinKnight Não, não parece. Aqui está a demonstração de trabalho: jsfiddle.net/XNwbt/1 e aqui está a demonstração com essas linhas removidas: jsfiddle.net/XNwbt/2
Šime Vidas
1
A rolagem manual está para trás no Firefox 10, pelo menos no Linux e Mac. Parece funcionar corretamente se você fizer isso -e.detail, testado no Firefox (Mac, Linux), Safari (Mac) e Chromium (Linux).
Anomie
1
@Anomie Correto. O sinal do Mozilla .detailnão corresponde ao sinal de .wheelData(que é o que o Chrome / IE tem), então temos que invertê-lo manualmente. Obrigado por corrigir minha resposta.
Šime Vidas
8
Eu usei isso, obrigado! Eu precisava adicionar este:if( e.originalEvent ) e = e.originalEvent;
Nikso
2
@ ŠimeVidas Fiz um ajuste a isto depois de implementá-lo no meu software. Não é crítico, mas possivelmente adiciona uma verificação de integridade para a propriedade scroll para evitar a rolagem de deadlock quando o elemento não tem rolagem. if ( $(this)[0].scrollHeight !== $(this).outerHeight() ) { //yourcode }será executado apenas quando houver uma barra de rolagem.
user0000001
30

Se você não se preocupa com a compatibilidade com versões mais antigas do IE (<8), pode criar um plugin jQuery customizado e então chamá-lo no elemento overflowing.

Esta solução tem uma vantagem sobre a proposta da Šime Vidas, pois não sobrescreve o comportamento de rolagem - apenas o bloqueia quando apropriado.

$.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
fonte
2
Obrigado! Acho que é mais apropriado não substituir o comportamento padrão. Para suporte a navegadores cruzados, pode ser usado o plugin jQuery Mouse Wheel .
Meettya
Infelizmente, isso parece problemático no Chrome 52 (OSX 10.10). Funciona perfeitamente no Safari.
gélido
1
Obrigado! As outras soluções tornaram a rolagem super lenta e
cheia de
@wojcikstefan, estou recebendo este aviso no 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

Acho que às vezes é possível cancelar o evento mousescroll: 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 do manipulador de eventos, você precisa saber:

  • direção de rolagem
    d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down' porque um número positivo significa rolagem para baixo
  • posição de rolagem
    scrollToppara cima, scrollHeight - scrollTop - offsetHeightpara baixo

Se vocês são

  • rolando para cima, e top = 0, ou
  • rolando para baixo, e bottom = 0,

cancelar o evento: e.preventDefault()(e talvez até e.stopPropagation()).

Acho que é melhor não substituir o comportamento de rolagem do navegador. Cancele apenas quando aplicável.

Provavelmente não é perfeitamente xbrowser, mas não pode ser muito difícil. Talvez a direção de rolagem dupla do Mac seja complicada ...

Rudie
fonte
2
como faço para implementar a mesma funcionalidade para dispositivos (tablets / telefones) eu acho que touchmove é o evento obrigatório
ViVekH
6

Use a propriedade CSS abaixo overscroll-behavior: contain;para o elemento filho

Prasoon Kumar
fonte
Todas as outras soluções são exageradas em comparação com esta regra CSS nativa. Bem feito.
TechWisdom
3

veja se isso ajuda você:

demo: jsfiddle

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

editar:

tente isto:

    $("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
fonte
Seus elementos são separados, enquanto um dos meus está contido no outro.
RIK
3

Uma solução menos hacky, na minha opinião, é definir o overflow oculto no corpo quando você passa o mouse sobre a div rolável. Isso evitará que o corpo role, mas ocorrerá um efeito de "salto" indesejado. A solução a seguir contorna isso:

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

Você pode tornar seu código mais inteligente, se necessário. Por exemplo, você pode testar se o corpo já tem um preenchimento e, em caso afirmativo, adicionar o novo preenchimento a ele.

Alexandru Lighezan
fonte
3

Na solução acima existe um pequeno erro em relação ao Firefox. No Firefox "DOMMouseScroll", o evento não tem propriedade e.detail, para obter essa propriedade você deve escrever o seguinte 'e.originalEvent.detail'.

Aqui está uma solução funcional para o 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
fonte
Isso funciona muito bem, exceto quando você bate na parte superior ou inferior da div, o primeiro evento carrega para o corpo. Testando com toque de 2 dedos em cromo em osx.
johnb003
3

aqui uma solução simples sem jQuery que não destrói a rolagem nativa do navegador (isto é: nenhuma rolagem artificial / feia):

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

Demonstração ao vivo: http://jsfiddle.net/ibcaliax/bwmzfmq7/4/

Iñaki Baz Castillo
fonte
Acabei de publicar uma biblioteca NPM implementando o código acima: npmjs.com/package/dontscrollthebody
Iñaki Baz Castillo
2

Aqui está minha solução que usei em aplicativos.

Desativei o estouro do corpo e coloquei todo o html do site dentro do contêiner div. Os contêineres do site têm estouro e, portanto, o usuário pode rolar a página conforme o esperado.

Em seguida, criei um div irmão (#Prevent) com um Z-index mais alto que cobre todo o site. Como #Prevent tem um Z-index mais alto, ele se sobrepõe ao contêiner do site. Quando #Prevent está visível, o mouse não está mais pairando sobre os contêineres do site, portanto a rolagem não é possível.

Você pode, é claro, colocar outro div, como seu modal, com um índice z maior do que #Prevent na marcação. Isso permite que você crie janelas pop-up que não sofram de problemas de rolagem.

Esta solução é melhor porque não esconde as barras de rolagem (efeito de salto). Não requer ouvintes de eventos e é fácil de implementar. Ele funciona em todos os navegadores, embora com o IE7 e 8 você tenha que brincar (depende do seu 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;
  }
}

desabilitar / habilitar a rolagem

PreventScroll('on'); // prevent scrolling
PreventScroll('off'); // allow scrolling
David D
fonte
2
Considere incluir algumas informações sobre sua resposta, em vez de simplesmente postar o código. Tentamos fornecer não apenas 'soluções', mas ajudar as pessoas a aprender. Você deve explicar o que estava errado no código original, o que você fez de maneira diferente e por que sua (s) alteração (ões) funcionaram.
Andrew Barber
1

Eu precisava adicionar este evento a vários elementos que podem ter uma barra de rolagem. Para os casos em que nenhuma barra de rolagem estava presente, a barra de rolagem principal não funcionou como deveria. Então, fiz uma pequena alteração no código @ Šime da seguinte maneira:

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

Agora, apenas elementos com uma barra de rolagem impedirão que a rolagem principal pare.

Ricky
fonte
0

Versão javascript pura da resposta de Vidas, el$é o nó DOM do plano que você está navegando.

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

Certifique-se de não anexar o par várias vezes! Aqui está um exemplo,

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

Atenção , a função precisa ser uma constante, se você reinicializar a função todas as vezes antes de anexá-la, ou seja, var func = function (...)o removeEventListenernão vai funcionar.

srcspider
fonte
0

Você pode fazer isso sem JavaScript. Você pode definir o estilo em ambos os divs para position: fixede overflow-y: auto. Você pode precisar tornar um deles mais alto do que o outro, definindo seu z-index(se eles se sobrepõem).

Aqui está um exemplo básico no CodePen .

Carl Smith
fonte
0

Aqui está o plugin que é útil para prevenir a rolagem dos pais ao rolar uma div específica e tem um monte de opções para brincar.

Confira aqui:

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

Uso

Acionar Scroll Lock via JavaScript:

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

Trigger Scroll Lock via Markup:

    <!-- 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 demonstração

MR_AMDEV
fonte
0

apenas oferecendo isso como uma solução possível se você não acha que o usuário terá uma experiência negativa na mudança óbvia. Eu simplesmente alterei a classe de estouro do corpo para oculta quando o mouse estava sobre a div de destino; então mudei o div do corpo para estouro oculto quando o mouse sai.

Pessoalmente, não acho que isso pareça ruim, meu código poderia usar alternância para torná-lo mais limpo, e há benefícios óbvios em tornar esse efeito possível sem que o usuário saiba. Portanto, esta é provavelmente a resposta de último recurso do hack.

//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";
}
o super-homem
fonte