web-dev-qa-db-fra.com

Empêcher le défilement du corps, mais autoriser le défilement en superposition

Je cherchais une solution de type "lightbox" permettant cela, mais je n'en ai pas encore trouvé (suggérez-en si vous en connaissez).

Le comportement que j'essaie de recréer est semblable à celui que vous verriez sur Pinterest lorsque vous cliquez sur une image. La superposition est défilable (comme dans toute la superposition se déplace comme une page en haut d'une page) mais le corps derrière la superposition est fixe.

J'ai essayé de créer cela avec uniquement du CSS (c'est-à-dire une incrustation div au-dessus de la page entière et du corps avec overflow: hidden), mais cela n'empêche pas div de défiler.

Comment empêcher le corps et la page de défiler tout en maintenant le défilement dans le conteneur plein écran?

355
PotatoFro

Théorie

Si vous examinez l'implémentation actuelle du site pinterest (cela pourrait changer dans le futur), lorsque vous ouvrez la superposition, une classe noscroll est appliquée à l'élément body et overflow: hidden est défini, de sorte que body ne peut plus défiler.

La superposition (créée à la volée ou déjà dans la page et rendue visible via display: block, cela ne fait aucune différence) a les propriétés position : fixed et overflow-y: scroll, avec top, left, right et bottom définies sur 0: ce style permet à la superposition de remplir fenêtre entière. 

La div à l'intérieur de la superposition est simplement dans position: static, alors la barre de défilement verticale que vous voyez est liée à cet élément. En conséquence, le contenu peut défiler mais la superposition reste fixe.

Lorsque vous fermez le zoom, vous masquez la superposition (via display: none) et vous pouvez également la supprimer entièrement via javascript (ou simplement le contenu qu'il contient, à vous de choisir comment l'injecter).

Enfin, vous devez également supprimer la classe noscroll de la body (la propriété de débordement revient donc à sa valeur initiale).


Code

Exemple Codepen

(cela fonctionne en modifiant l'attribut aria-hidden de la superposition afin de la montrer et de la masquer et d'augmenter son accessibilité).

Balisage
(bouton ouvert)

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

(superposition et bouton de fermeture)} _

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

});

Enfin, voici un autre exemple dans lequel la superposition s'ouvre avec un effet de fondu par une CSS transition appliquée à la propriété opacity. De plus, un padding-right est appliqué pour éviter une refonte du texte sous-jacent lorsque la barre de défilement disparaît.

Exemple 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; 
}
542
fcalderan

Si vous souhaitez éviter le sur-défilement sur ios, vous pouvez ajouter une position fixée à votre classe .noscroll.

body.noscroll{
    position:fixed;
    overflow:hidden;
}
67
am80l

N'utilisez pas overflow: hidden; sur body. Il fait automatiquement défiler tout vers le haut. Il n'y a pas besoin de JavaScript non plus. Utilisez overflow: auto;. Cette solution fonctionne même avec Safari mobile:

Structure HTML

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

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

Coiffant

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

Voir la démo ici et le code source ici .

Mettre à jour:

Pour les personnes souhaitant travailler avec la barre d’espace du clavier, la page vers le haut ou le bas: vous devez vous concentrer sur la superposition, par exemple en cliquant dessus ou manuellement sur JS avant que cette partie de la div réponde au clavier. Même chose lorsque la superposition est "désactivée", car elle ne fait que déplacer la superposition sur le côté. Autrement, il ne s'agit que de deux variables divs normales et il ne sait pas pourquoi il convient de se concentrer sur l'une d'elles.

36
Luxiyalu

Il est à noter que parfois, ajouter "overflow: hidden" à la balise body ne fait pas le travail. Dans ces cas, vous devrez également ajouter la propriété à la balise HTML.

html, body {
    overflow: hidden;
}
28
Francisco Hodge

La plupart des solutions ont le problème de ne pas conserver la position de défilement, alors j'ai jeté un œil à la façon dont Facebook le fait. En plus de définir le contenu sous-jacent sur position: fixed, ils définissent également le haut de manière dynamique pour conserver la position de défilement:

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

Ensuite, lorsque vous retirez à nouveau la superposition, vous devez réinitialiser la position de défilement:

window.scrollTo(0, scrollPosition);

J'ai créé un petit exemple pour illustrer cette solution

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>

22
Philipp Mitterer

La propriété overscroll-behavior css permet de remplacer le comportement de défilement par débordement par défaut du navigateur lorsqu'il atteint le haut/le bas du contenu.

Ajoutez simplement les styles suivants à superposer:

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

Démo Codepen

Fonctionne actuellement dans Chrome, Firefox et IE ( caniuse )

Pour plus de détails, consultez article sur les développeurs google .

18
Igor Alemasow

En règle générale, si vous souhaitez qu'un parent (le corps dans ce cas) l'empêche de défiler lorsqu'un enfant (la superposition dans ce cas) défile, faites de l'enfant un frère du parent pour empêcher l'événement de le parent. Dans le cas où le parent est le corps, cela nécessite un élément d'emballage supplémentaire:

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

Voir Faites défiler un contenu particulier avec la barre de défilement principale du navigateur pour voir son fonctionnement.

6
NGLN

La réponse choisie est correcte mais présente certaines limites:

  • Les "lancers" super durs avec votre doigt feront toujours défiler <body> en arrière-plan
  • Ouvrir le clavier virtuel en appuyant sur un <input> dans le modal dirigera tous les futurs parchemins vers <body>

Je n'ai pas de solution pour le premier problème, mais je voulais faire la lumière sur le second. Confusément, Bootstrap avait l'habitude de documenter le problème de clavier, mais ils ont affirmé qu'il avait été corrigé, citant http://output.jsbin.com/cacido/quiet comme exemple de ce correctif.

En effet, cet exemple fonctionne bien sur iOS avec mes tests. Cependant, la mise à niveau vers la dernière version de Bootstrap (v4) l’interrompt.

Pour tenter de comprendre la différence entre eux, j'ai réduit un scénario de test pour qu'il ne dépende plus de Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG .

Les facteurs décisifs sont bizarres. Pour éviter le problème lié au clavier, il semble que background-colorn'est pas défini à la racine <div> contenant le modal et le contenu du modal doit être imbriqué dans un autre <div>, pouvant avoir background-color défini.

Pour tester, décommentez la ligne ci-dessous dans l'exemple 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; */
}
5
Weston

Pour les appareils tactiles, essayez d’ajouter un div transparent transparent de 1px de large, 101vh_min-height dans le wrapper de la superposition. Ajoutez ensuite -webkit-overflow-scrolling:touch; overflow-y: auto; au wrapper. Cela incite le safari mobile à penser que la superposition est défilable, interceptant ainsi l'événement tactile du corps.

Voici un exemple de page. Ouvert sur safari mobile: http://www.originalfunction.com/overlay.html

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

4
YarGnawh

J'ai trouvé cette question en essayant de résoudre le problème que j'avais avec ma page sur Ipad et Iphone - corps défilait lorsque j'affichais div fixe en popup avec image.

Certaines réponses sont bonnes, mais aucune d’entre elles n’a résolu mon problème. J'ai trouvé l'article suivant sur le blog de Christoffer Pettersson. La solution présentée ici a aidé le problème que j'avais avec les appareils iOS et mon problème de défilement en arrière-plan. 

Six choses que j'ai apprises sur le défilement élastique de iOS Safari

Comme il a été suggéré, j'inclus les points principaux de l'article de blog au cas où le lien deviendrait obsolète. 

"Afin de désactiver le fait que l'utilisateur puisse faire défiler la page d'arrière-plan pendant que le" menu est ouvert ", il est possible de contrôler quels éléments doivent être autorisés à faire défiler ou non, en appliquant du JavaScript et une classe CSS.

Sur la base de ce Stackoverflow answer, vous pouvez contrôler que les éléments dotés du désactive-défilement ne doivent pas exécuter Effectuer leur action de défilement par défaut lorsque l'événement touchmove est déclenché. "

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

Et mettez ensuite la classe disable-scrolling sur la page div:

<div class="page disable-scrolling">
3
TheFullResolution

J'aimerais ajouter aux réponses précédentes parce que j'ai essayé de le faire, et une mise en page s'est brisée dès que j'ai basculé le corps en position: résolu. Afin d'éviter cela, je devais également régler la taille du corps à 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");
}
1
Vic Seedoubleyew

Si vous voulez arrêter body/html scroll, ajoutez ce qui suit:

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>

Fondamentalement, vous pouvez le faire sans JS.

L'idée principale est d'ajouter html/body avec une hauteur: 100% et un dépassement: auto . Et dans votre incrustation, vous pouvez activer/désactiver le défilement en fonction de vos besoins.

J'espère que cela t'aides!

0
Ashwin

Si l'intention est de désactiver les appareils mobiles/tactiles, le moyen le plus simple de le faire consiste à utiliser touch-action: none;.

Exemple:

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>

(L'exemple ne fonctionne pas dans le contexte du dépassement de capacité. Vous devrez le recréer dans une page autonome.)

Si vous souhaitez désactiver le défilement du conteneur #app, ajoutez simplement touch-action: none;.

0
Gajus

Le comportement que vous souhaitez empêcher s'appelle le chaînage de défilement. Pour le désactiver, définissez

overscroll-behavior: contain;

sur votre superposition en CSS.

0
黄雨伞

essaye ça

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