Comment empêcher le défilement de la page lors du défilement d'un élément DIV?

94

J'ai revu et testé les différentes fonctions pour empêcher le corps de faire défiler à l'intérieur d'un div et j'ai combiné une fonction qui devrait fonctionner.

$('.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;
    });
});
  • Cela arrête tout défilement là où je veux que le défilement soit toujours possible à l'intérieur du conteneur
  • Cela ne se désactive pas non plus en cas d'absence de la souris

Des idées, ou de meilleures façons de faire cela?

RIK
la source
Vous définissez le corps comme retour faux de la molette de la souris, c'est peut-être le problème, je suppose que votre conteneur est à l'intérieur du corps à droite
Ricardo Binns
@ric_bfa oui mais comment y remédier
RIK
à la place du corps, définissez votre conteneur de classe / id
Ricardo Binns
Je devrais peut-être clarifier. Lorsque la souris est à l'intérieur de l'élément '.scrollable', la capacité du corps à faire défiler doit être désactivée
RIK
4
N'y a-t-il pas un moyen de faire cela avec tous les CSS et pas de JavaScript?
chharvey

Réponses:

165

Mise à jour 2: Ma solution est basée sur la désactivation totale du défilement natif du navigateur (lorsque le curseur est à l'intérieur du DIV) puis sur le défilement manuel du DIV avec JavaScript (en définissant sa .scrollToppropriété). Une approche alternative et meilleure de l'OMI serait de désactiver uniquement sélectivement le défilement du navigateur afin d'empêcher le défilement de la page, mais pas le défilement DIV. Découvrez la réponse de Rudie ci-dessous qui illustre cette solution.


Voici:

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

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

Démo en direct: https://jsbin.com/howojuq/edit?js,output

Ainsi, vous définissez manuellement la position de défilement, puis vous évitez simplement le comportement par défaut (qui serait de faire défiler la DIV ou la page Web entière).

Mise à jour 1: Comme Chris l'a noté dans les commentaires ci-dessous, dans les versions plus récentes de jQuery, les informations delta sont imbriquées dans l' .originalEventobjet, c'est-à-dire que jQuery ne les expose plus dans son objet Event personnalisé et nous devons les récupérer à partir de l'objet Event natif à la place .

Šime Vidas
la source
2
@RobinKnight Non, cela ne semble pas. Voici la démo de travail: jsfiddle.net/XNwbt/1 et voici la démo avec ces lignes supprimées: jsfiddle.net/XNwbt/2
Šime Vidas
1
Votre défilement manuel est en arrière sur Firefox 10, au moins sur Linux et Mac. Semble fonctionner correctement si vous faites cela -e.detail, testé dans Firefox (Mac, Linux), Safari (Mac) et Chromium (Linux).
Anomie
1
@Anomie C'est correct. Le signe de Mozilla .detailne correspond pas au signe de .wheelData(qui est ce que Chrome / IE a), nous devons donc l'inverser manuellement. Merci d'avoir corrigé ma réponse.
Šime Vidas
8
J'ai utilisé ceci, merci! J'avais besoin d'ajouter ceci:if( e.originalEvent ) e = e.originalEvent;
Nikso
2
@ ŠimeVidas J'ai fait un ajustement à cela après l'avoir implémenté dans mon logiciel. Pas critique mais peut-être ajouter une vérification de cohérence pour la propriété scroll pour empêcher le défilement de l'interblocage lorsque l'élément n'a pas de défilement. if ( $(this)[0].scrollHeight !== $(this).outerHeight() ) { //yourcode }ne s'exécutera que s'il y a une barre de défilement.
user0000001
30

Si vous ne vous souciez pas de la compatibilité avec les anciennes versions d'IE (<8), vous pouvez créer un plugin jQuery personnalisé, puis l'appeler sur l'élément débordant.

Cette solution a un avantage par rapport à celle proposée par Šime Vidas, car elle n'écrase pas le comportement de défilement - elle le bloque simplement le cas échéant.

$.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
la source
2
Merci! Je pense qu'il est plus approprié de ne pas écraser le comportement par défaut. Pour la prise en charge de plusieurs navigateurs, il peut être utilisé le plugin jQuery Mouse Wheel .
Meettya
Malheureusement, cela semble problématique dans Chrome 52 (OSX 10.10). Fonctionne parfaitement dans Safari, cependant.
frosty
1
Je vous remercie! Les autres solutions ont rendu le défilement super lent et
bogué
@wojcikstefan, je reçois cet avertissement dans 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

Je pense qu'il est parfois possible d'annuler l'événement 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();
});

Dans le gestionnaire d'événements, vous aurez besoin de savoir:

  • sens de défilement
    d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down' car un nombre positif signifie défilement vers le bas
  • position de défilement
    scrollToppour le haut, scrollHeight - scrollTop - offsetHeightpour le bas

Si vous êtes

  • défilement vers le haut, et top = 0, ou
  • défilement vers le bas, et bottom = 0,

annuler l'événement: e.preventDefault()(et peut-être même e.stopPropagation()).

Je pense qu'il vaut mieux ne pas remplacer le comportement de défilement du navigateur. Ne l'annulez que le cas échéant.

Ce n'est probablement pas parfaitement xbrowser, mais cela ne peut pas être très difficile. Peut-être que la double direction de défilement de Mac est délicate cependant ...

Rudie
la source
2
comment mettre en œuvre la même fonctionnalité pour les appareils (tablettes / téléphones) je suppose que touchmove est l'événement de liaison
ViVekH
6

Utilisez la propriété CSS ci-dessous overscroll-behavior: contain;pour l'élément enfant

Prasoon Kumar
la source
Toutes les autres solutions sont exagérées, par rapport à cette règle CSS native. Bien fait.
TechWisdom
3

voyez si cela vous aide:

démo: jsfiddle

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

Éditer:

essaye ça:

    $("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
la source
Vos éléments sont séparés, tandis que l'un des miens est contenu dans l'autre.
RIK du
3

Une solution moins hacky, à mon avis, est de définir le débordement caché sur le corps lorsque vous passez la souris sur le div à défilement. Cela empêchera le corps de défiler, mais un effet de "saut" indésirable se produira. La solution suivante fonctionne autour de cela:

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

Vous pouvez rendre votre code plus intelligent si nécessaire. Par exemple, vous pouvez tester si le corps a déjà un remplissage et si oui, ajouter le nouveau remplissage à cela.

Alexandru Lighezan
la source
3

Dans la solution ci-dessus, il y a une petite erreur concernant Firefox. Dans Firefox, l'événement "DOMMouseScroll" n'a pas de propriété e.detail, pour obtenir cette propriété, vous devez écrire 'e.originalEvent.detail' suivant.

Voici une solution de travail pour 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
la source
Cela fonctionne plutôt bien, sauf lorsque vous vous écrasez en haut ou en bas de la div que le premier événement porte dans le corps. Test avec 2 doigts sur Chrome dans OSX.
johnb003
3

voici une solution simple sans jQuery qui ne détruit pas le scroll natif du navigateur (c'est: pas de scrolling artificiel / laid):

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

Démo en direct: http://jsfiddle.net/ibcaliax/bwmzfmq7/4/

Iñaki Baz Castillo
la source
Je viens de publier une bibliothèque NPM implémentant le code ci-dessus: npmjs.com/package/dontscrollthebody
Iñaki Baz Castillo
2

Voici ma solution que j'ai utilisée dans les applications.

J'ai désactivé le débordement de corps et placé le html du site Web entier dans les div de conteneurs. Les conteneurs du site Web ont un débordement et l'utilisateur peut donc faire défiler la page comme prévu.

J'ai ensuite créé un div frère (#Prevent) avec un z-index plus élevé qui couvre l'ensemble du site Web. Comme #Prevent a un z-index plus élevé, il chevauche le conteneur du site Web. Lorsque #Prevent est visible, la souris ne survole plus les conteneurs du site Web, le défilement n'est donc pas possible.

Vous pouvez bien sûr placer un autre div, tel que votre modal, avec un z-index supérieur à #Prevent dans le balisage. Cela vous permet de créer des fenêtres pop-up qui ne souffrent pas de problèmes de défilement.

Cette solution est meilleure car elle ne cache pas les barres de défilement (effet de saut). Il ne nécessite pas d'écouteurs d'événements et est facile à mettre en œuvre. Cela fonctionne dans tous les navigateurs, bien qu'avec IE7 et 8, vous devez jouer (dépend de votre code spécifique).

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

désactiver / activer le défilement

PreventScroll('on'); // prevent scrolling
PreventScroll('off'); // allow scrolling
David D
la source
2
Veuillez envisager d'inclure des informations sur votre réponse, plutôt que de simplement publier un code. Nous essayons non seulement de fournir des «correctifs», mais d'aider les gens à apprendre. Vous devez expliquer ce qui n'allait pas dans le code d'origine, ce que vous avez fait différemment et pourquoi vos modifications ont fonctionné.
Andrew Barber
1

J'avais besoin d'ajouter cet événement à plusieurs éléments qui pourraient avoir une barre de défilement. Dans les cas où aucune barre de défilement n'était présente, la barre de défilement principale ne fonctionnait pas comme elle le devrait. J'ai donc apporté une petite modification au code @ Šime comme suit:

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

Désormais, seuls les éléments avec une barre de défilement empêcheront le défilement principal de commencer à s'arrêter.

Ricky
la source
0

La version purement javascript de la réponse de Vidas el$est le nœud DOM du plan que vous faites défiler.

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

Assurez-vous de ne pas attacher même plusieurs fois! Voici un exemple,

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

Attention , la fonction doit y avoir une constante, si vous réinitialisez la fonction à chaque fois avant de l'attacher, c'est à dire. var func = function (...)le removeEventListenerne fonctionnera pas.

srcspider
la source
0

Vous pouvez le faire sans JavaScript. Vous pouvez définir le style des deux div sur position: fixedet overflow-y: auto. Vous devrez peut-être rendre l'un d'entre eux plus haut que l'autre en définissant son z-index(s'ils se chevauchent).

Voici un exemple de base sur CodePen .

Carl Smith
la source
0

Voici le plugin qui est utile pour empêcher le défilement des parents lors du défilement d'un div spécifique et a un tas d'options avec lesquelles jouer.

Vérifiez le ici:

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

Usage

Déclenchez le verrouillage du défilement via JavaScript:

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

Déclenchez le verrouillage du défilement via le balisage:

    <!-- 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>

Voir la démo

MR_AMDEV
la source
0

il suffit de proposer cela comme solution possible si vous ne pensez pas que l'utilisateur aura une expérience négative du changement évident. J'ai simplement changé la classe de débordement du corps en hidden lorsque la souris était sur le div cible; puis j'ai changé le div du corps en débordement caché lorsque la souris part.

Personnellement, je ne pense pas que cela ait l'air mal, mon code pourrait utiliser toggle pour le rendre plus propre, et il y a des avantages évidents à rendre cet effet possible sans que l'utilisateur en soit conscient. C'est donc probablement la réponse hackish de dernier recours.

//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
la source