Impedir a rolagem do corpo, mas permitir a rolagem de sobreposição

445

Estive procurando por uma solução do tipo "caixa de luz" que permita isso, mas ainda não a encontrei (sugira se souber de alguma).

O comportamento que estou tentando recriar é igual ao que você veria no Pinterest ao clicar em uma imagem. A sobreposição é rolável ( como em toda a sobreposição se move para cima como uma página na parte superior de uma página ), mas o corpo atrás da sobreposição é fixo.

Tentei criar isso apenas com CSS ( ou seja, uma divsobreposição na parte superior da página e no corpooverflow: hidden ), mas isso não impede a divrolagem.

Como impedir a rolagem do corpo / página, mas a rolagem dentro do contêiner de tela cheia?

PotatoFro
fonte
este não é apenas um plugin de "overlay" como hundrets por aí usando posição fixa com rolagem overflow-y?
ggzone
3
O link para o Pinterest não ajuda, pois seu conteúdo está atrás de um mural de login.
2540625
4
Atualização de 2017: mesmo que você faça login no Pinterest, o que você descobrirá é que o efeito de sobreposição descrito no OP não existe mais - ao clicar em uma imagem, basta navegar para uma página comum que exibe uma versão grande da imagem.
23917 Annabel

Respostas:

623

Teoria

Observando a implementação atual do site do pinterest (isso pode mudar no futuro), quando você abre a sobreposição, uma noscrollclasse é aplicada ao bodyelemento e overflow: hiddené definida, portanto, bodynão é mais rolável.

A sobreposição (criado on-the-fly ou já dentro da página e tornada visível através display: block, não faz diferença) tem position : fixede overflow-y: scroll, com top, left, righte bottompropriedades definido para0 : esse estilo faz a sobreposição de preencher toda a janela.

A parte divinterna da sobreposição é apenas position: statica barra de rolagem vertical que você vê relacionada a esse elemento. Como resultado, o conteúdo é rolável, mas a sobreposição permanece fixa.

Quando você fecha o zoom, oculta a sobreposição (via display: none) e também pode removê-la completamente via javascript (ou apenas o conteúdo interno, cabe a você como injetá-la).

Como etapa final, você também deve remover a noscrollclasse para body(para que a propriedade overflow retorne ao seu valor inicial)


Código

Exemplo de Codepen

(funciona alterando o aria-hiddenatributo da sobreposição para mostrá-lo e ocultá-lo e aumentar sua acessibilidade).

Marcação
(botão aberto)

<button type="button" class="open-overlay">OPEN LAYER</button>

(botão de sobreposição e fechamento)

<section class="overlay" aria-hidden="true">
  <div>
    <h2>Hello, I'm the overlayer</h2>
    ...   
    <button type="button" class="close-overlay">CLOSE LAYER</button>
  </div>
</section>

CSS

.noscroll { 
  overflow: hidden;
}

.overlay { 
   position: fixed; 
   overflow-y: scroll;
   top: 0; right: 0; bottom: 0; left: 0; }

[aria-hidden="true"]  { display: none; }
[aria-hidden="false"] { display: block; }

Javascript (baunilha-JS)

var body = document.body,
    overlay = document.querySelector('.overlay'),
    overlayBtts = document.querySelectorAll('button[class$="overlay"]');

[].forEach.call(overlayBtts, function(btt) {

  btt.addEventListener('click', function() { 

     /* Detect the button class name */
     var overlayOpen = this.className === 'open-overlay';

     /* Toggle the aria-hidden state on the overlay and the 
        no-scroll class on the body */
     overlay.setAttribute('aria-hidden', !overlayOpen);
     body.classList.toggle('noscroll', overlayOpen);

     /* On some mobile browser when the overlay was previously
        opened and scrolled, if you open it again it doesn't 
        reset its scrollTop property */
     overlay.scrollTop = 0;

  }, false);

});

Finalmente, aqui está outro exemplo no qual a sobreposição é aberta com um efeito de desvanecimento por um CSS transitionaplicado à opacitypropriedade. Também padding-righté aplicado um para evitar um refluxo no texto subjacente quando a barra de rolagem desaparecer.

Exemplo de Codepen (fade)

CSS

.noscroll { overflow: hidden; }

@media (min-device-width: 1025px) {
    /* not strictly necessary, just an experiment for 
       this specific example and couldn't be necessary 
       at all on some browser */
    .noscroll { 
        padding-right: 15px;
    }
}

.overlay { 
     position: fixed; 
     overflow-y: scroll;
     top: 0; left: 0; right: 0; bottom: 0;
}

[aria-hidden="true"] {    
    transition: opacity 1s, z-index 0s 1s;
    width: 100vw;
    z-index: -1; 
    opacity: 0;  
}

[aria-hidden="false"] {  
    transition: opacity 1s;
    width: 100%;
    z-index: 1;  
    opacity: 1; 
}
Fabrizio Calderan
fonte
6
Essa é a resposta correta. Alguém deve dar uma marca de seleção, pois está correto.
Scoota P
64
Isso é absolutamente correto, mas lembre-se de que não funciona no safari móvel. O plano de fundo continuará rolando e sua sobreposição será corrigida.
precisa saber é
20
Funciona bem. O contêiner de janela de rolagem divdeve ter estilo CSS position:fixede rolagem de estouro vertical. Usei com sucesso overflow-y:auto;e, para rolagem de momento / inércia do iOS, adicionei -webkit-overflow-scrolling:touch;ao CSS. Eu usei display:block;, width:100%;e height:100%;CSS para ter uma janela de exibição de página inteira.
Slink
10
Desculpe me, eu não entendo. Definir o corpo para transbordar: oculto não desativa a rolagem do corpo elástico no meu iPad iOS7. Então eu tive que adicionar dentro dos meus js: document.body.addEventListener ('touchmove', função (evento) {event.preventDefault ();}, false); que SADLY também desabilita a rolagem de todos os elementos. Não encontrei nenhuma solução até agora (sem plugins adicionais)
Garavani
22
Se o usuário já rolou o corpo de baixo para baixo quando a sobreposição é ativada, isso fará com que o corpo salte para cima quando você definir o estouro: oculto; Alguma maneira de contornar isso?
Björn Andersson
75

Se você deseja evitar o excesso de rolagem no ios, é possível adicionar a posição fixa à sua classe .noscroll

body.noscroll{
    position:fixed;
    overflow:hidden;
}
am80l
fonte
106
Com esta solução, devido à posição fixa, o corpo rolará automaticamente para a parte superior do seu conteúdo. Isso pode ser perturbador para seus usuários.
tzi
5
Outro problema com o uso position:fixedé que ele estava redimensionando meu corpo principal. Talvez tenha sido em conflito com outro CSS, mas overflow:hiddené tudo o que era necessário
Dex
3
isso está quebrando salto foco quando você guia através de campos de entrada
Atav32
@ Atav32 Parece um problema, posição e estouro separados, não devem afetar a ordem das guias.
Am80l 26/08/2015
@ am80l desculpe o meu comentário anterior foi super vago: a ordem de tabulação obras encontrar, mas os solavancos de tela quando você guia através de campos no iOS Safari ( stackoverflow.com/questions/31126330/... )
Atav32
44

Não use overflow: hidden;on body. Rola automaticamente tudo para o topo. Também não há necessidade de JavaScript. Fazer uso deoverflow: auto; . Essa solução ainda funciona com o Safari móvel:

Estrutura HTML

<div class="overlay">
    <div class="overlay-content"></div>
</div>

<div class="background-content">
    lengthy content here
</div>

Styling

.overlay{
    position: fixed;
    top: 0px;
    left: 0px;
    right: 0px;
    bottom: 0px;
    background-color: rgba(0, 0, 0, 0.8);

    .overlay-content {
        height: 100%;
        overflow: scroll;
    }
}

.background-content{
    height: 100%;
    overflow: auto;
}

Veja a demonstração aqui e o código fonte aqui .

Atualizar:

Para as pessoas que desejam que a barra de espaço do teclado funcione, faça a página para cima / para baixo: você precisa se concentrar na sobreposição, por exemplo, clicando nela, ou manualmente, JS focando nela antes que essa parte do divteclado responda. O mesmo acontece quando a sobreposição é "desativada", pois está apenas movendo a sobreposição para o lado. Caso contrário, para o navegador, estes são apenas dois se normais dive não saberia por que deveria se concentrar em nenhum deles.

Lucia
fonte
5
arrefecer solução, mas então eu preciso envolver todo o meu conteúdo em uma div, que eu não tinha intenção de fazer ...
Jacob Raccuia
2
Esta é uma solução ideal. Se alguém estiver com problemas, talvez seja necessário adicionar html, body { height: 100%; }(como na demonstração) para que isso funcione corretamente.
John John
4
@ user18490 a peça angular / material não tem nada a ver com o fato de que esta solução está funcionando.
Lucia
10
Isso interrompe o UX de dispositivos móveis de várias maneiras (por exemplo, ocultação de barra de URL, disponibilidade de excesso de rolagem, ...), até a barra de espaço para rolar nos desktops.
nitely
2
Essa é a opção mais robusta, mas complica um pouco as coisas. Por exemplo, o PageUp e o PageDown não funcionarão na atualização. qualquer coisa que usa os .offset()valores para o cálculo fica confuso, etc ...
BlackPanther
42

overscroll-behavior A propriedade css permite substituir o comportamento de rolagem de estouro padrão do navegador ao atingir a parte superior / inferior do conteúdo.

Basta adicionar os seguintes estilos para sobrepor:

.overlay {
   overscroll-behavior: contain;
   ...
}

Codepen demo

Atualmente trabalha no Chrome, Firefox e IE ( caniuse )

Para mais detalhes, consulte o artigo de desenvolvedores do Google .

Igor Alemasow
fonte
4
Eu vim até o fim para dizer que isso está funcionando no Chrome, Mozilla e Opera mais recentes. Tenha um excelente momento!
Wannabe JavaGeek
3
Alguém deve adicionar uma recompensa por isso. Esta é a solução correta. Não é necessário JavaScript. :)
joseph.l.hunsaker
9
O problema com esta solução é que ela só funciona se a sobreposição for rolável. Se você possui um modal e tudo se encaixa na tela, para que não haja rolagem dentro - ela não interromperá a rolagem do corpo. Isso e também não funciona no Safari em todos os :)
waterplea
1
Eu decidi com uma combinação disso com overflow-y: scroll(da sua demo do Codepen). O argumento levantado por @pokrishka é válido, mas no meu caso não é uma preocupação. De qualquer forma, gostaria de saber se as implementações dos navegadores cobrirão esses detalhes no futuro. Descobri que, no Firefox, redimensionando o navegador para que o modal não caiba na tela e redimensionando-o novamente para que essa propriedade funcione (por exemplo, a rolagem é contida) mesmo após o redimensionamento para o tamanho original - até que a página seja recarregada , finalmente.
Marc.2377
33

A maioria das soluções tem o problema de não manter a posição de rolagem, então dei uma olhada em como o Facebook faz isso. Além de definir o conteúdo subjacente, position: fixedeles também definem a parte superior dinamicamente para manter a posição de rolagem:

scrollPosition = window.pageYOffset;
mainEl.style.top = -scrollPosition + 'px';

Em seguida, quando você remover a sobreposição novamente, precisará redefinir a posição de rolagem:

window.scrollTo(0, scrollPosition);

Criei um pequeno exemplo para demonstrar esta solução

let overlayShown = false;
let scrollPosition = 0;

document.querySelector('.toggle').addEventListener('click', function() {
  if (overlayShown) {
		showOverlay();
  } else {
    removeOverlay();
  }
  overlayShown = !overlayShown;
});

function showOverlay() {
    scrollPosition = window.pageYOffset;
  	const mainEl = document.querySelector('.main-content');
    mainEl.style.top = -scrollPosition + 'px';
  	document.body.classList.add('show-overlay');
}

function removeOverlay() {
		document.body.classList.remove('show-overlay');
  	window.scrollTo(0, scrollPosition);
    const mainEl = document.querySelector('.main-content');
    mainEl.style.top = 0;
}
.main-content {
  background-image: repeating-linear-gradient( lime, blue 103px);
  width: 100%;
  height: 200vh;
}

.show-overlay .main-content {
  position: fixed;
  left: 0;
  right: 0;
  overflow-y: scroll; /* render disabled scroll bar to keep the same width */
}

.overlay {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.3);
  overflow: auto;
}

.show-overlay .overlay {
  display: block;
}

.overlay-content {
  margin: 50px;
  background-image: repeating-linear-gradient( grey, grey 20px, black 20px, black 40px);
  height: 120vh;
}

.toggle {
  position: fixed;
  top: 5px;
  left: 15px;
  padding: 10px;
  background: red;
}

/* reset CSS */
body {
  margin: 0;
}
<main class="main-content"></main>

  <div class="overlay">
    <div class="overlay-content"></div>
  </div>
  
  <button class="toggle">Overlay</button>

Philipp Mitterer
fonte
Esta parece ser a solução mais elegante para mim. Eu sei que o Google emprega os mesmos truques de rolar a página e mover elementos com frequência.
Forallepsilon 03/07/19
Esta é a única solução que funcionou 100% correta para mim, gostaria de encontrar esta resposta anteriormente.
User0103 16/07/19
31

Vale notar que, às vezes, adicionar "overflow: hidden" à tag body não funciona. Nesses casos, você também precisará adicionar a propriedade à tag html.

html, body {
    overflow: hidden;
}
Francisco Hodge
fonte
Para iOS, você precisa também definirwidth: 100%; height: 100%;
Gavin
2
Isso não resolve um problema também observado em muitas das outras soluções para esta pergunta - se a janela for rolada para qualquer lugar, exceto a parte superior da página quando o modal estiver aberto, fará com que a rolagem seja exibida na parte superior da página. Encontrei a solução de Philipp Mitterer aqui como a melhor opção que cobre a maioria dos casos.
CLL
1
Eu nunca tive esse problema de que você fala (na Web ou em dispositivos móveis). Você poderia compartilhar o código completo (com DOM) que não funciona em um violino ou jsbin? De qualquer forma, tenho receio de usar soluções alternativas de Javascript para solucionar esse problema.
Francisco Hodge
7

De um modo geral, se você deseja que um pai (o corpo neste caso) impeça a rolagem quando uma criança (a sobreposição nesse caso) rola, faça do filho um irmão do pai para impedir que o evento de rolagem borbulhe até o pai. No caso de o pai ser o corpo, isso requer um elemento adicional de empacotamento:

<div id="content">
</div>
<div id="overlay">
</div>

Consulte Rolar conteúdo específico de DIV com a barra de rolagem principal do navegador para ver seu funcionamento.

NGLN
fonte
1
A melhor solução, o verdadeiro pensamento "pronto para uso", mas não tão bem formulado.
Mark
Se o corpo não for o elemento que rola, as barras superiores não deslizarão para cima no celular enquanto você rola para baixo na página.
Seph Reed
7

A resposta escolhida está correta, mas tem algumas limitações:

  • Os "arremessos" super duros com o dedo ainda rolam <body> no fundo
  • A abertura do teclado virtual tocando <input>em um no modal direcionará todos os pergaminhos futuros para<body>

Não tenho uma correção para o primeiro problema, mas queria esclarecer o segundo. Confusamente, o Bootstrap costumava documentar o problema do teclado, mas eles alegaram que o problema foi corrigido, citando http://output.jsbin.com/cacido/quiet como um exemplo da correção.

Na verdade, esse exemplo funciona bem no iOS com meus testes. No entanto, atualizá-lo para o Bootstrap mais recente (v4) o interrompe.

Na tentativa de descobrir qual era a diferença entre eles, reduzi um caso de teste para não depender mais do Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG .

Os fatores decisivos são bizarros. Evitar o problema do teclado parece exigir que background-color não esteja definido na raiz que <div>contém o modal e o conteúdo do modal deve ser aninhado em outro <div>, que possa ter sido background-colordefinido.

Para testá-lo, remova o comentário da linha abaixo no exemplo do Codepen:

.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  display: none;
  overflow: hidden;
  -webkit-overflow-scrolling: touch;
  /* UNCOMMENT TO BREAK */
/*   background-color: white; */
}
Weston
fonte
4

Para dispositivos sensíveis ao toque, tente adicionar uma 101vhdiv transparente de 1px de largura e altura mínima no invólucro da sobreposição. Então adicione-webkit-overflow-scrolling:touch; overflow-y: auto; ao invólucro. Isso faz o safari móvel pensar que a sobreposição é rolável, interceptando o evento de toque do corpo.

Aqui está uma página de amostra. Abra no safari móvel: http://www.originalfunction.com/overlay.html

https://gist.github.com/YarGnawh/90e0647f21b5fa78d2f678909673507f

YarGnawh
fonte
Jup, isso fez o truque. Bastava adicionar -webkit-overflow-scrolling:touch;para cada contêiner rolável para interceptar eventos de toque.
Mati
Ainda sou capaz de rolar no iPhone
Simone Zandara
@SeniorSimoneZandara Eu não sou. esta multa trabalha para prevenir fundo do rolo
godblessstrawberry
3

O comportamento que você deseja impedir é chamado de encadeamento de rolagem. Para desativá-lo, defina

overscroll-behavior: contain;

na sua sobreposição em CSS.

黄 雨伞
fonte
3

Eu encontrei esta pergunta tentando resolver o problema que tive com minha página no Ipad e no iPhone - o corpo estava rolando quando eu exibia div fixo como pop-up com imagem.

Algumas respostas são boas, mas nenhuma delas resolveu meu problema. Encontrei a seguinte postagem no blog de Christoffer Pettersson. A solução apresentada ajudou a solucionar o problema que tive com os dispositivos iOS e o meu problema de rolagem em segundo plano.

Seis coisas que aprendi sobre a rolagem de elástico do iOS Safari

Como foi sugerido, incluo os principais pontos da postagem do blog, caso o link fique desatualizado.

"Para desativar que o usuário possa rolar a página de plano de fundo enquanto o" menu está aberto ", é possível controlar quais elementos devem ou não ser rolados, aplicando algum JavaScript e uma classe CSS.

Com base nesta resposta do Stackoverflow, você pode controlar se os elementos com a rolagem de desativação não devem executar sua ação de rolagem padrão quando o evento touchmove for acionado. "

 document.ontouchmove = function ( event ) {

    var isTouchMoveAllowed = true, target = event.target;

    while ( target !== null ) {
        if ( target.classList && target.classList.contains( 'disable-scrolling' ) ) {
            isTouchMoveAllowed = false;
            break;
        }
        target = target.parentNode;
    }

    if ( !isTouchMoveAllowed ) {
        event.preventDefault();
    }
};

E, em seguida, coloque a classe desativar-rolagem na página div:

<div class="page disable-scrolling">
TheFullResolution
fonte
2

Você pode fazer isso facilmente com alguns "novos" css e JQuery.

Inicialmente: body {... overflow:auto;} Com o JQuery, você pode alternar dinamicamente entre 'overlay' e 'body'. Quando estiver no 'corpo', use

body {
   position: static;
   overflow: auto;
}

Quando em 'overlay', use

body {
   position: sticky;
   overflow: hidden;
}

JQuery para o switch ('body' -> 'overlay'):

$("body").css({"position": "sticky", "overflow": "hidden"});

JQuery para o switch ('overlay' -> 'body'):

$("body").css({"position": "static", "overflow": "auto"});
christk
fonte
1

Eu gostaria de adicionar respostas anteriores porque tentei fazer isso, e algum layout foi interrompido assim que mudei o corpo para a posição: fixo. Para evitar isso, eu também precisava definir a altura do corpo para 100%:

function onMouseOverOverlay(over){
    document.getElementsByTagName("body")[0].style.overflowY = (over?"hidden":"scroll");
    document.getElementsByTagName("html")[0].style.position = (over?"fixed":"static");
    document.getElementsByTagName("html")[0].style.height = (over?"100%":"auto");
}
Vic Seedoubleyew
fonte
1

Use o seguinte HTML:

<body>
  <div class="page">Page content here</div>
  <div class="overlay"></div>
</body>

Em seguida, JavaScript para interceptar e parar de rolar:

$(".page").on("touchmove", function(event) {
  event.preventDefault()
});

Então, para que as coisas voltem ao normal:

$(".page").off("touchmove");

Damir Kotoric
fonte
1

Se a intenção é desativar em dispositivos móveis / touch, a maneira mais direta de fazer isso é usando touch-action: none;.

Exemplo:

const app = document.getElementById('app');
const overlay = document.getElementById('overlay');

let body = '';

for (let index = 0; index < 500; index++) {
  body += index + '<br />';
}

app.innerHTML = body;
app.scrollTop = 200;

overlay.innerHTML = body;
* {
  margin: 0;
  padding: 0;
}

html,
body {
  height: 100%;
}

#app {
  background: #f00;
  position: absolute;
  height: 100%;
  width: 100%;
  overflow-y: scroll;
  line-height: 20px;
}

#overlay {
  background: rgba(0,0,0,.5);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100%;
  padding: 0 0 0 100px;
  overflow: scroll;
}
<div id='app'></div>
<div id='overlay'></div>

(O exemplo não funciona no contexto de Estouro de pilha. Você precisará recriá-lo em uma página independente.)

Se você deseja desativar a rolagem do #appcontêiner, basta adicionar touch-action: none;.

Gajus
fonte
1
Se a ação de toque realmente funcionou no iOS, isso é. E, nesse caso, não seria necessário agrupar todo o "aplicativo" em uma div separada.
PowerBuoy
0

tente isso

var mywindow = $('body'), navbarCollap = $('.navbar-collapse');    
navbarCollap.on('show.bs.collapse', function(x) {
                mywindow.css({visibility: 'hidden'});
                $('body').attr("scroll","no").attr("style", "overflow: hidden");
            });
            navbarCollap.on('hide.bs.collapse', function(x) {
                mywindow.css({visibility: 'visible'});
                $('body').attr("scroll","yes").attr("style", "");
            });
Junaid
fonte
0

Se você deseja interromper a rolagem body / html, adicione o seguinte

CSS

    html, body {
        height: 100%;
    }

    .overlay{
        position: fixed;
        top: 0px;
        left: 0px;
        right: 0px;
        bottom: 0px;
        background-color: rgba(0, 0, 0, 0.8);

        .overlay-content {
            height: 100%;
            overflow: scroll;
        }
    }

    .background-content{
        height: 100%;
        overflow: auto;
    }

HTML

    <div class="overlay">
        <div class="overlay-content"></div>
    </div>

    <div class="background-content">
        lengthy content here
    </div>

Basicamente, você poderia fazer isso sem JS.

A idéia principal é adicionar html / body com height: 100% e overflow: auto. e dentro da sua sobreposição, você pode ativar / desativar a rolagem com base nos seus requisitos.

Espero que isto ajude!

Ashwin
fonte
-2

Use o código abaixo para desativar e ativar a barra de rolagem.

Scroll = (
    function(){
          var x,y;
         function hndlr(){
            window.scrollTo(x,y);
            //return;
          }  
          return {

               disable : function(x1,y1){
                    x = x1;
                    y = y1;
                   if(window.addEventListener){
                       window.addEventListener("scroll",hndlr);
                   } 
                   else{
                        window.attachEvent("onscroll", hndlr);
                   }     

               },
               enable: function(){
                      if(window.removeEventListener){
                         window.removeEventListener("scroll",hndlr);
                      }
                      else{
                        window.detachEvent("onscroll", hndlr);
                      }
               } 

          }
    })();
 //for disabled scroll bar.
Scroll.disable(0,document.body.scrollTop);
//for enabled scroll bar.
Scroll.enable();
Rakesh Chaudhari
fonte