Evita el desplazamiento del cuerpo pero permite el desplazamiento de superposición

446

He estado buscando una solución de tipo "caja de luz" que permita esto, pero aún no he encontrado una (por favor, sugiera si conoce alguna).

El comportamiento que intento recrear es como el que verías en Pinterest al hacer clic en una imagen. La superposición es desplazable ( como en toda la superposición se mueve hacia arriba como una página en la parte superior de una página ) pero el cuerpo detrás de la superposición está fijo.

Intenté crear esto solo con CSS ( es decir, una divsuperposición en la parte superior de toda la página y el cuerpooverflow: hidden ), pero no impide que se divpueda desplazar.

¿Cómo evitar que el cuerpo / página se desplace pero siga desplazándose dentro del contenedor de pantalla completa?

Patata
fuente
¿No es esto simplemente un complemento de "superposición" como hundrets por ahí usando la posición fija con desplazamiento de desbordamiento y?
ggzone
3
El enlace a Pinterest no ayuda, ya que su contenido está detrás de un muro de inicio de sesión.
2540625
44
Actualización 2017: incluso si inicia sesión en Pinterest, lo que encuentra es que el efecto de superposición descrito en el OP ya no existe; en cambio, cuando hace clic en una imagen, simplemente navega a una página normal que muestra una versión grande de la imagen.
Annabel

Respuestas:

624

Teoría

Al observar la implementación actual del sitio pinterest (puede cambiar en el futuro), cuando abre la superposición noscroll, se aplica una clase al bodyelemento y overflow: hiddense establece, por bodylo que ya no se puede desplazar.

La superposición (creado en la marcha o ya dentro de la página y se hace visible a través display: block, no hay diferencia) tiene position : fixedy overflow-y: scroll, con top, left, righty bottompropiedades definidas 0: este estilo hace que la superposición de llenar toda la ventana gráfica.

El divinterior de la superposición está en su lugar justo position: staticentonces, la barra de desplazamiento vertical que ves está relacionada con ese elemento. Como resultado, el contenido es desplazable pero la superposición permanece fija.

Cuando cierra el zoom, oculta la superposición (a través de display: none) y luego también puede eliminarla por completo a través de javascript (o solo el contenido dentro, depende de usted cómo inyectarlo).

Como paso final, también debe eliminar la noscrollclase body(para que la propiedad de desbordamiento vuelva a su valor inicial)


Código

Ejemplo de Codepen

(funciona cambiando el aria-hiddenatributo de la superposición para mostrarlo y ocultarlo y aumentar su accesibilidad).

Marcado
(botón de apertura)

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

(botón de superposición y cierre)

<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 (vanilla-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, aquí hay otro ejemplo en el que la superposición se abre con un efecto de desvanecimiento por un CSS transitionaplicado a la opacitypropiedad. También padding-rightse aplica a para evitar un reflujo en el texto subyacente cuando la barra de desplazamiento desaparece.

Ejemplo de codepen (desvanecimiento)

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
fuente
66
Esta es la respuesta correcta. Alguien debería darle una marca de verificación, ya que es correcto.
Scoota P
64
Esto es absolutamente correcto, pero ten en cuenta que no funciona en el safari móvil. El fondo continuará desplazándose y su superposición será reparada.
a10s
20
Funciona bien. El contenedor de viewport desplazable divdebe tener un estilo CSS position:fixedy un desplazamiento vertical de desbordamiento. Utilicé con éxito overflow-y:auto;y para el desplazamiento de impulso / inercia de iOS, agregué -webkit-overflow-scrolling:touch;al CSS. Solía display:block;, width:100%;y height:100%;CSS para tener una vista de página completa.
Slink
10
Lo siento, no entiendo. Configurar el cuerpo para que se desborde: oculto no desactiva el desplazamiento del cuerpo elástico en mi iPad iOS7. Así que tuve que agregar dentro de mi js: document.body.addEventListener ('touchmove', function (event) {event.preventDefault ();}, false); que SADLY deshabilita todos los elementos de ser desplazables, también. No he encontrado ninguna solución hasta el momento (sin enchufes adicionales)
Garavani
22
Si el usuario ya ha desplazado el cuerpo hacia abajo cuando se activa la superposición, esto hará que el cuerpo salte hacia arriba cuando configure el desbordamiento: oculto; ¿Alguna forma de evitar esto?
Björn Andersson
75

Si desea evitar el desplazamiento excesivo en ios, puede agregar una posición fija a su clase .noscroll

body.noscroll{
    position:fixed;
    overflow:hidden;
}
am80l
fuente
106
Con esta solución, debido a la posición fija, el cuerpo se desplazará automáticamente a la parte superior de su contenido. Esto podría ser molesto para sus usuarios.
tzi
55
Otro problema con el uso position:fixedes que estaba cambiando el tamaño de mi cuerpo principal. Tal vez estaba en conflicto con otros CSS, pero overflow:hiddenes todo lo que se necesitaba
Dex
3
esto es romper el salto enfoque cuando salta a través de campos de entrada
Atav32
@ Atav32 Suena como un problema separado, la posición y el desbordamiento no deberían afectar el orden de tabulación.
am80l
@ am80l, lo siento, mi comentario anterior fue muy vago: el orden de tabulación funciona, pero la pantalla se sacude cuando pasas por los campos en Safari de iOS ( stackoverflow.com/questions/31126330/… )
Atav32
44

No utilice overflow: hidden;el body. Desplaza automáticamente todo a la parte superior. No hay necesidad de JavaScript tampoco. Hacer uso deoverflow: auto; . Esta solución incluso funciona con Safari móvil:

Estructura HTML

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

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

Estilo

.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;
}

Vea la demostración aquí y el código fuente aquí .

Actualizar:

Para las personas que desean la barra espaciadora del teclado, la página arriba / abajo funciona: debe enfocarse en la superposición, por ejemplo, hacer clic en ella, o JS se enfoca manualmente antes de que esta parte del divteclado responda. Lo mismo ocurre cuando la superposición está "apagada", ya que solo está moviendo la superposición hacia un lado. De lo contrario para el navegador, estos son solo dos divs normales y no sabría por qué debería centrarse en ninguno de ellos.

Lucia
fuente
55
solución genial, pero luego necesito envolver todo mi contenido en un div, que no tenía intención de hacer ...
Jacob Raccuia
2
Esta es una solución ideal. Si alguien tiene problemas con esto, es posible que deba agregar html, body { height: 100%; }(como en la demostración) para que esto funcione correctamente.
John
44
@ user18490 la parte angular / material no tiene nada que ver con el hecho de que esta solución está funcionando.
Lucia
10
Esto rompe la UX de los dispositivos móviles de varias maneras (es decir, ocultación de la barra de URL, Overscroll Affordance, ...), incluso la barra espaciadora para desplazarse en los escritorios.
Nitely
2
Esta es la opción más robusta, pero complica un poco las cosas. Por ejemplo, PageUp y PageDown no funcionarán en la actualización. cualquier cosa que utiliza los .offset()valores para el cálculo se cometa un error, etc ...
BlackPanther
42

overscroll-behavior La propiedad css permite anular el comportamiento de desplazamiento de desbordamiento predeterminado del navegador al llegar a la parte superior / inferior del contenido.

Simplemente agregue los siguientes estilos para superponer:

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

Codepen demo

Actualmente funciona en Chrome, Firefox e IE ( caniuse )

Para más detalles, consulte el artículo de desarrolladores de Google .

Igor Alemasow
fuente
44
He venido hasta el final para decirte que esto está funcionando en los últimos Chrome, Mozilla y Opera. ¡Tenga un maravilloso momento!
Wannabe JavaGeek
3
Alguien debería agregar una recompensa por esto. Esta es la solución correcta. No es necesario JavaScript. :)
joseph.l.hunsaker
99
El problema con esta solución es que solo funciona si la superposición es desplazable. Si tiene un modal y todo cabe en la pantalla, por lo que no hay desplazamiento dentro, no detendrá el desplazamiento del cuerpo. Eso y tampoco funciona en Safari en absoluto :)
waterplea
1
overflow-y: scrollResolví con una combinación de esto con (de su demostración de Codepen). El punto planteado por @pokrishka es válido, pero en mi caso no es una preocupación. En cualquier caso, me pregunto si las implementaciones de los navegadores cubrirán este detalle en el futuro. Descubrí que, en Firefox, se cambia el tamaño del navegador para que el modal no se ajuste a la pantalla y luego se cambia el tamaño nuevamente para que haga que esta propiedad funcione (es decir, el desplazamiento está contenido) incluso después de cambiar el tamaño a tamaño completo, hasta que la página se vuelva a cargar , al menos.
Marc.2377
33

La mayoría de las soluciones tienen el problema de que no retienen la posición de desplazamiento, así que eché un vistazo a cómo lo hace Facebook. Además de configurar el contenido subyacente position: fixed, también configuran la parte superior dinámicamente para mantener la posición de desplazamiento:

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

Luego, cuando elimine la superposición nuevamente, debe restablecer la posición de desplazamiento:

window.scrollTo(0, scrollPosition);

Creé un pequeño ejemplo para demostrar esta solución

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
fuente
Esta parece ser la solución más elegante para mí. Sé que Google emplea los mismos trucos para desplazarse por la página y mover elementos a menudo.
forallepsilon
Esta es la única solución que funcionó 100% correcta para mí, desearía haber encontrado esta respuesta antes.
user0103
31

Vale la pena señalar que a veces agregar "desbordamiento: oculto" a la etiqueta del cuerpo no funciona. En esos casos, también deberá agregar la propiedad a la etiqueta html.

html, body {
    overflow: hidden;
}
Francisco Hodge
fuente
Para iOS, también debe configurarlowidth: 100%; height: 100%;
Gavin, el
2
Esto no soluciona un problema que también se ve en muchas de las otras soluciones para esta pregunta: si la ventana se desplaza a cualquier lugar que no sea la parte superior de la página cuando el modal está abierto, provocará un desplazamiento hacia la parte superior de la página. Encontré que la solución de Philipp Mitterer aquí es la mejor opción que cubre la mayoría de los casos.
CLL
1
Nunca he experimentado este problema del que hablas (ya sea en la web o en dispositivos móviles). ¿Podría compartir el código completo (con DOM) que no funciona en un violín o jsbin? En cualquier caso, desconfío de usar soluciones alternativas de Javascript para abordar este problema.
Francisco Hodge
7

En términos generales, si desea que un padre (el cuerpo en este caso) evite que se desplace cuando un niño (la superposición en este caso) se desplaza, entonces haga que el niño sea hermano del padre para evitar que el evento de desplazamiento aumente el padre. En el caso de que el padre sea el cuerpo, esto requiere un elemento de envoltura adicional:

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

Consulte Desplazar contenidos DIV particulares con la barra de desplazamiento principal del navegador para ver su funcionamiento.

NGLN
fuente
1
La mejor solución, el verdadero pensamiento "fuera de la caja", pero no tan bien redactado.
Mark
Si el cuerpo no es el elemento que se desplaza, las barras superiores no se deslizarán hacia arriba en el móvil a medida que se desplaza hacia abajo en la página.
Seph Reed
7

La respuesta elegida es correcta, pero tiene algunas limitaciones:

  • Los "lanzamientos" súper duros con el dedo aún se desplazarán <body>en el fondo
  • Al abrir el teclado virtual tocando un <input>en el modal, todos los desplazamientos futuros se dirigirán a<body>

No tengo una solución para el primer problema, pero quería arrojar algo de luz sobre el segundo. Confusamente, Bootstrap solía documentar el problema del teclado, pero afirmaron que se solucionó, citando http://output.jsbin.com/cacido/quiet como un ejemplo de la solución.

De hecho, ese ejemplo funciona bien en iOS con mis pruebas. Sin embargo, actualizarlo al último Bootstrap (v4) lo rompe.

En un intento por descubrir cuál era la diferencia entre ellos, reduje un caso de prueba para que ya no dependiera de Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG .

Los factores decisivos son extraños. Evitar el problema del teclado parece requerir que background-color no se establezca en la raíz que <div>contiene el modal y el contenido del modal debe estar anidado en otro <div>, que puede haber background-colorestablecido.

Para probarlo, descomente la línea siguiente en el ejemplo de 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
fuente
4

Para dispositivos táctiles, intente agregar un 101vhdiv transparente de 1px de ancho y altura mínima en la envoltura de la superposición. Luego añade-webkit-overflow-scrolling:touch; overflow-y: auto; al envoltorio. Esto engaña al safari móvil para que piense que la superposición es desplazable, interceptando así el evento táctil del cuerpo.

Aquí hay una página de muestra. Abrir en safari móvil: http://www.originalfunction.com/overlay.html

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

YarGnawh
fuente
Jup, eso hizo el truco. Solo tenía que agregar -webkit-overflow-scrolling:touch;para cada contenedor desplazable para interceptar eventos táctiles.
Mati
Todavía puedo desplazarme en iPhone
Simone Zandara
@SeniorSimoneZandara no lo soy. esto funciona bien para evitar el desplazamiento de fondo
godblessstrawberry
3

El comportamiento que desea evitar se llama encadenamiento de desplazamiento. Para deshabilitarlo, establezca

overscroll-behavior: contain;

en tu superposición en CSS.

黄 雨伞
fuente
3

Encontré esta pregunta tratando de resolver el problema que tenía con mi página en Ipad y Iphone: el cuerpo se desplazaba cuando mostraba div fijo como ventana emergente con imagen.

Algunas respuestas son buenas, sin embargo, ninguna de ellas resolvió mi problema. Encontré la siguiente publicación de blog de Christoffer Pettersson. La solución presentada allí ayudó al problema que tenía con los dispositivos iOS y ayudó a mi problema de fondo de desplazamiento.

Seis cosas que aprendí sobre el desplazamiento de la banda de goma de iOS Safari

Como se sugirió, incluyo los puntos principales de la publicación del blog en caso de que el enlace quede desactualizado.

"Para deshabilitar que el usuario pueda desplazarse por la página de fondo mientras el" menú está abierto ", es posible controlar qué elementos deben permitirse desplazarse o no, aplicando JavaScript y una clase CSS.

Según esta respuesta de Stackoverflow , puede controlar que los elementos con el desplazamiento de desactivación no realicen su acción de desplazamiento predeterminada cuando se activa el evento de movimiento táctil ".

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

Y luego coloque la clase de desplazamiento de desactivación en la página div:

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

Puede hacerlo fácilmente con algunos "nuevos" css y JQuery.

Inicialmente: body {... overflow:auto;} con JQuery puede cambiar dinámicamente entre 'superposición' y 'cuerpo'. Cuando esté en 'cuerpo', use

body {
   position: static;
   overflow: auto;
}

Cuando está en uso 'superpuesto'

body {
   position: sticky;
   overflow: hidden;
}

JQuery para el conmutador ('cuerpo' -> 'superposición'):

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

JQuery para el conmutador ('superposición' -> 'cuerpo'):

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

Me gustaría agregar a las respuestas anteriores porque intenté hacer eso, y algunos diseños se rompieron tan pronto como cambié el cuerpo a la posición: fijo. Para evitar eso, también tuve que establecer la altura del cuerpo al 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
fuente
1

Use el siguiente HTML:

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

Luego JavaScript para interceptar y detener el desplazamiento:

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

Luego, para que las cosas vuelvan a la normalidad:

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

Damir Kotoric
fuente
1

Si la intención es deshabilitar en dispositivos móviles / táctiles, entonces la forma más sencilla de hacerlo es mediante el uso touch-action: none;.

Ejemplo:

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>

(El ejemplo no funciona en el contexto de Desbordamiento de pila. Deberá volver a crearlo en una página independiente).

Si desea deshabilitar el desplazamiento del #appcontenedor, simplemente agregue touch-action: none;.

Gajus
fuente
1
Si la acción táctil realmente funcionó en iOS, eso es. Y en ese caso no habría necesidad de envolver toda su "aplicación" en un div separado.
powerbuoy
0

prueba esto

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
fuente
0

Si desea detener el desplazamiento del cuerpo / html, agregue lo siguiente

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>

Básicamente, podrías hacerlo sin JS.

La idea principal es agregar html / body con height: 100% y overflow: auto. y dentro de su superposición, puede habilitar / deshabilitar el desplazamiento según sus requisitos.

¡Espero que esto ayude!

Ashwin
fuente
-2

Use el código a continuación para deshabilitar y habilitar la barra de desplazamiento.

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
fuente