web-dev-qa-db-fra.com

Empêcher le défilement de l'élément parent lorsque la position de défilement de l'élément intérieur atteint le haut/le bas?

J'ai une petite "boîte à outils flottante" - une div avec position:fixed; overflow:auto. Fonctionne très bien.

Mais lorsque vous faites défiler l'intérieur de cette boîte (avec la molette de la souris) et que vous atteignez le bas OR haut, l'élément parent "prend en charge" la "demande de défilement": le document situé derrière la boîte à outils défile.
- Ce qui est embêtant et non ce que l'utilisateur a "demandé".

J'utilise jQuery et je pensais pouvoir arrêter ce problème avec event.stoppropagation ():
$("#toolBox").scroll( function(event){ event.stoppropagation() });

Il entre dans la fonction, mais la propagation a quand même lieu (le document défile)
- Il est étonnamment difficile de rechercher ce sujet sur SO (et Google). Je dois donc demander:
Comment empêcher la propagation/la propagation de l'événement scroll?

Modifier:
Solution de travail grâce à amustill (et Brandon Aaron pour le plug-in mousewheel ici:
https://github.com/brandonaaron/jquery-mousewheel/raw/master/jquery.mousewheel.js

$(".ToolPage").bind('mousewheel', function(e, d)  
    var t = $(this);
    if (d > 0 && t.scrollTop() === 0) {
        e.preventDefault();
    }
    else {
        if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
            e.preventDefault();
        }
    }
});
180
T4NK3R
37
amustill

J'ajoute cette réponse pour qu'elle soit complète parce que la réponse acceptée par @amustill ne résout pas correctement le problème dans Internet Explorer. Veuillez voir les commentaires dans mon post original pour plus de détails. De plus, cette solution ne nécessite aucun plugin, seulement jQuery.

En substance, le code fonctionne en gérant l'événement mousewheel. Chacun de ces événements contient un wheelDelta égal au nombre de px auquel il va déplacer la zone de défilement. Si cette valeur est >0, alors nous faisons défiler up. Si wheelDelta est <0, nous faisons défiler down

FireFox: FireFox utilise DOMMouseScroll comme événement et remplit originalEvent.detail, dont +/- est inversé par rapport à ce qui est décrit ci-dessus. Il renvoie généralement les intervalles de 3, tandis que les autres navigateurs renvoient le défilement par intervalles de 120 (au moins sur ma machine). Pour corriger, il suffit de le détecter et de le multiplier par -40 pour le normaliser.

@ amustill's answer fonctionne en annulant l'événement si la zone de défilement du <div> se trouve déjà en haut ou en bas de la position maximale. Cependant, Internet Explorer ignore l'événement annulé dans les cas où delta est plus grand que l'espace défilant restant.

En d’autres termes, si vous avez un 200px tall <div> contenant 500px de contenu défilable et si le scrollTop actuel est 400, un événement mousewheel qui indique au navigateur de faire défiler 120px entraînera également le défilement du <div> et du <body>, car 400 + 120> 500.

Donc, pour résoudre le problème, nous devons faire quelque chose de légèrement différent, comme indiqué ci-dessous:

Le code requis pour jQuery est le suivant:

$(document).on('DOMMouseScroll mousewheel', '.Scrollable', function(ev) {
    var $this = $(this),
        scrollTop = this.scrollTop,
        scrollHeight = this.scrollHeight,
        height = $this.innerHeight(),
        delta = (ev.type == 'DOMMouseScroll' ?
            ev.originalEvent.detail * -40 :
            ev.originalEvent.wheelDelta),
        up = delta > 0;

    var prevent = function() {
        ev.stopPropagation();
        ev.preventDefault();
        ev.returnValue = false;
        return false;
    }

    if (!up && -delta > scrollHeight - height - scrollTop) {
        // Scrolling down, but this will take us past the bottom.
        $this.scrollTop(scrollHeight);
        return prevent();
    } else if (up && delta > scrollTop) {
        // Scrolling up, but this will take us past the top.
        $this.scrollTop(0);
        return prevent();
    }
});

Essentiellement, ce code annule tout événement de défilement qui créerait la condition Edge indésirable, puis utilise jQuery pour définir le scrollTop du <div> sur la valeur maximale ou minimale, en fonction de la direction demandée par l'événement mousewheel.

Dans la mesure où l'événement est entièrement annulé, il ne se propage jamais du tout vers body et résout donc le problème dans IE, ainsi que dans tous les autres navigateurs.

J'ai également mis en place un exemple de travail sur jsFiddle .

165
Troy Alford

EDIT: Exemple CodePen

Pour AngularJS, j'ai défini la directive suivante:

module.directive('isolateScrolling', function () {
  return {
    restrict: 'A',
      link: function (scope, element, attr) {
        element.bind('DOMMouseScroll', function (e) {
          if (e.detail > 0 && this.clientHeight + this.scrollTop == this.scrollHeight) {
            this.scrollTop = this.scrollHeight - this.clientHeight;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }
          else if (e.detail < 0 && this.scrollTop <= 0) {
            this.scrollTop = 0;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }
        });
        element.bind('mousewheel', function (e) {
          if (e.deltaY > 0 && this.clientHeight + this.scrollTop >= this.scrollHeight) {
            this.scrollTop = this.scrollHeight - this.clientHeight;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }
          else if (e.deltaY < 0 && this.scrollTop <= 0) {
            this.scrollTop = 0;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }

          return true;
        });
      }
  };
});

Et l'a ajouté ensuite à l'élément scrollable (le menu déroulant ul):

<div class="dropdown">
  <button type="button" class="btn dropdown-toggle">Rename <span class="caret"></span></button>
  <ul class="dropdown-menu" isolate-scrolling>
    <li ng-repeat="s in savedSettings | objectToArray | orderBy:'name' track by s.name">
      <a ng-click="renameSettings(s.name)">{{s.name}}</a>
    </li>
  </ul>
</div>

Testé sur Chrome et Firefox. Le défilement régulier de Chrome élimine ce piratage lorsqu'un mouvement important de la molette est effectué près (mais pas au-dessus) du haut ou du bas de la zone de défilement.

19
dOxxx

Je sais que c’est une question assez ancienne, mais comme c’est l’un des meilleurs résultats dans google ... j’ai dû annuler d’une manière ou d’une autre le scroll bullbling sans jQuery et ce code me convient:

function preventDefault(e) {
  e = e || window.event;
  if (e.preventDefault)
    e.preventDefault();
  e.returnValue = false;  
}

document.getElementById('a').onmousewheel = function(e) { 
  document.getElementById('a').scrollTop -= e. wheelDeltaY; 
  preventDefault(e);
}
19
Nebril

Toutes les solutions données dans ce fil ne mentionnent pas un moyen existant - et natif - de résoudre ce problème sans réorganiser le DOM et/ou utiliser des astuces permettant d'éviter les événements. Mais il y a une bonne raison: cette méthode est exclusive et disponible uniquement sur la plate-forme Web MS. Citant MSDN :

Propriété -ms-scroll-chaining - spécifie le comportement de défilement qui se produit lorsqu'un utilisateur dépasse la limite de défilement lors d'une manipulation. Valeurs de propriété:

chained - Valeur initiale. L'élément parent de défilement le plus proche commence à défiler lorsque l'utilisateur atteint une limite de défilement lors d'une manipulation. Aucun effet de rebond n'est affiché.

none - Un effet de rebond est affiché lorsque l'utilisateur atteint une limite de défilement lors d'une manipulation.

Accordée, cette propriété est prise en charge sur IE10 +/Edge uniquement. Pourtant, voici un tell quote :

Pour vous donner une idée de la popularité de la prévention de l'enchaînement des rouleaux, selon ma recherche rapide dans les archives http "-ms-scroll-chaining: none" est utilisé dans 0,4% des 300 000 pages les plus visitées malgré une limitation en fonctionnalité et uniquement pris en charge sur IE/Edge.

Et maintenant, bonne nouvelle, tout le monde! À partir de Chrome 63, nous avons enfin un remède natif pour les plates-formes Blink - à la fois Chrome (évidemment) et Android WebView (bientôt). 

Citant l'article d'introduction :

La propriété overscroll-behavior est une nouvelle fonctionnalité CSS qui contrôle le comportement de ce qui se passe lorsque vous faites un survol excessif d'un conteneur (y compris la page elle-même). Vous pouvez l’utiliser pour annuler le chaînage de défilement, désactiver/personnaliser l’action d’actualisation, désactivez la bande élastique effets sur iOS (lorsque Safari implémente le comportement de super-défilement), et plus encore. [...]

La propriété prend trois valeurs possibles:

auto - Par défaut. Les parchemins provenant de l'élément peuvent se propager vers éléments d'ancêtre. 

contenir - empêche le chaînage de défilement. Les parchemins ne le font pas se propager aux ancêtres mais les effets locaux dans le nœud sont montrés . Par exemple, l’effet de lueur de survol sur Android ou le effet élastique sur iOS qui avertit l'utilisateur quand il a frappé un limite de défilement. Remarque: en utilisant le comportement overscroll: contenir sur le code HTML element empeche les actions de navigation en incrustation.

none - Identique à contenir, mais empêche également les effets de survol à l'intérieur du nœud lui-même (par exemple, lueur de survol Android ou bande élastique iOS).

[...] La meilleure partie est que l'utilisation d'un comportement de défilement excessif ne nuit pas affecter les performances de la page comme les hacks mentionnés dans l'intro!

Voici cette feature in action . Et voici le document de module CSS correspondant.

UPDATE: Firefox, depuis la version 59, a rejoint le club, et MS Edge devrait implémenter cette fonctionnalité dans la version 18. Voici le caniusage correspondant.

11
raina77ow

Il y a des tas de questions comme celle-ci, avec de nombreuses réponses, mais je ne trouvais pas de solution satisfaisante ne comportant pas d'événements, de scripts, de plugins, etc. Je voulais que cela reste clair en HTML et en CSS. J'ai finalement trouvé une solution qui fonctionnait bien qu'il faille restructurer le balisage pour rompre la chaîne des événements.


1. Problème de base

L’entrée de défilement (c’est-à-dire la molette) appliquée à l’élément modal déborde sur un élément ancêtre et le fait défiler dans le même sens, si un tel élément est susceptible de défilement:

(Tous les exemples doivent être visualisés sur des résolutions de postes de travail))

https://jsfiddle.net/ybkbg26c/5/

HTML:

<div id="parent">
  <div id="modal">
    This text is pretty long here.  Hope fully, we will get some scroll bars.
  </div>
</div>

CSS:

#modal {
  position: absolute;
  height: 100px;
  width: 100px;
  top: 20%;
  left: 20%;
  overflow-y: scroll;
}
#parent {
  height: 4000px;
}

2. Pas de défilement parent sur le défilement modal

La raison pour laquelle l'ancêtre finit par faire défiler le site est due au fait que l'événement de défilement bouillonne et qu'un élément de la chaîne est capable de le gère. Pour y remédier, vous pouvez vous assurer qu'aucun des éléments de la chaîne ne sait comment gérer le parchemin. En termes de notre exemple, nous pouvons refactoriser l'arborescence pour déplacer le modal en dehors de l'élément parent. Pour des raisons obscures, il ne suffit pas de conserver le parent et les frères et sœurs modaux du DOM; le parent doit être encapsulé par un autre élément établissant un nouveau contexte d'empilement. Un emballage parfaitement positionné autour du parent peut faire l'affaire. 

Le résultat obtenu est que tant que le modal reçoit l'événement de défilement, l'événement ne sera pas transmis à l'élément "parent". 

En règle générale, il devrait être possible de repenser l’arborescence DOM afin de prendre en charge ce comportement sans affecter ce que l’utilisateur final voit.

https://jsfiddle.net/0bqq31Lv/3/

HTML:

<div id="context">
  <div id="parent">
  </div>
</div>
<div id="modal">
  This text is pretty long here.  Hope fully, we will get some scroll bars.
</div>

CSS (nouveau seulement):

#context {
  position: absolute;
  overflow-y: scroll;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

3. Pas de défilement n'importe où sauf en mode modal tant qu'il est actif

La solution ci-dessus permet toujours au parent de recevoir des événements de défilement tant qu’ils ne sont pas interceptés par la fenêtre modale (c’est-à-dire s’ils sont déclenchés par la molette de la souris alors que le curseur n’est pas au-dessus de la modale). Cela est parfois indésirable et nous voudrons peut-être interdire tout défilement en arrière-plan pendant que le modal est activé. Pour ce faire, nous devons insérer un contexte d'empilement supplémentaire qui couvre toute la fenêtre derrière le modal. Nous pouvons le faire en affichant une superposition positionnée de manière absolue, qui peut être totalement transparente si nécessaire (mais pas visibility:hidden).

https://jsfiddle.net/0bqq31Lv/2/

HTML:

<div id="context">
  <div id="parent">
  </div>
</div>
<div id="overlay">  
</div>
<div id="modal">
  This text is pretty long here.  Hope fully, we will get some scroll bars.
</div>

CSS (nouveau sur le n ° 2):

#overlay {
  background-color: transparent;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
10
Daniel S.

Directive JS angulaire

Je devais envelopper une directive angulaire. Ce qui suit est un mashup des autres réponses ici. testé sur Chrome et Internet Explorer 11. 

var app = angular.module('myApp');

app.directive("preventParentScroll", function () {
    return {
        restrict: "A",
        scope: false,
        link: function (scope, Elm, attr) {
            Elm.bind('mousewheel', onMouseWheel);
            function onMouseWheel(e) {
                Elm[0].scrollTop -= (e.wheelDeltaY || (e.originalEvent && (e.originalEvent.wheelDeltaY || e.originalEvent.wheelDelta)) || e.wheelDelta || 0);
                e.stopPropagation();
                e.preventDefault();
                e.returnValue = false;
            }
        }
    }
});

Usage

<div prevent-parent-scroll>
    ...
</div>

J'espère que cela aidera la prochaine personne à arriver d'une recherche Google.

9
Jossef Harush

En variante, pour éviter les problèmes de performances lors de la manipulation de scroll ou mousewheel, vous pouvez utiliser le code suivant:

css:

body.noscroll {
    overflow: hidden;
}
.scrollable {
    max-height: 200px;
    overflow-y: scroll;
    border: 1px solid #ccc;
}

html:

<div class="scrollable">
...A bunch of items to make the div scroll...
</div>
...A bunch of text to make the body scroll...

js:

var $document = $(document),
    $body = $('body'),
    $scrolable = $('.scrollable');

$scrolable.on({
          'mouseenter': function () {
            // add hack class to prevent workspace scroll when scroll outside
            $body.addClass('noscroll');
          },
          'mouseleave': function () {
            // remove hack class to allow scroll
            $body.removeClass('noscroll');
          }
        });

Exemple de travail: http://jsbin.com/damuwinarata/4

8
Bohdan Lyzanets

Voici une version JavaScript simple:

function scroll(e) {
  var delta = (e.type === "mousewheel") ? e.wheelDelta : e.detail * -40;
  if (delta < 0 && (this.scrollHeight - this.offsetHeight - this.scrollTop) <= 0) {
    this.scrollTop = this.scrollHeight;
    e.preventDefault();
  } else if (delta > 0 && delta > this.scrollTop) {
    this.scrollTop = 0;
    e.preventDefault();
  }
}
document.querySelectorAll(".scroller").addEventListener("mousewheel", scroll);
document.querySelectorAll(".scroller").addEventListener("DOMMouseScroll", scroll);
7
silverwind

Utilisation des propriétés de défilement d'éléments natifs avec la valeur delta du plugin mousewheel:

$elem.on('mousewheel', function (e, delta) {
    // Restricts mouse scrolling to the scrolling range of this element.
    if (
        this.scrollTop < 1 && delta > 0 ||
        (this.clientHeight + this.scrollTop) === this.scrollHeight && delta < 0
    ) {
        e.preventDefault();
    }
});
7
Pete B

Si quelqu'un cherche toujours une solution à cela, le plugin suivant fait le travail http://mohammadyounes.github.io/jquery-scrollLock/

Il résout complètement le problème du verrouillage du défilement de la molette de la souris dans un conteneur donné, l'empêchant de se propager à l'élément parent.

Cela ne change pas la vitesse de défilement de la roue, l'expérience de l'utilisateur ne sera pas affectée. et vous obtenez le même comportement quelle que soit la vitesse de défilement vertical de la molette de la souris du système d’exploitation (Sous Windows, il est possible de définir un écran ou une ligne jusqu’à 100 lignes par cran).

Démo: http://mohammadyounes.github.io/jquery-scrollLock/example/

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

6
MK.

la méthode ci-dessus n’est pas si naturelle, après quelques recherches sur Google, j’ai trouvé une solution plus agréable, sans avoir besoin de jQuery. voir [1] et démo [2].

  var element = document.getElementById('uf-notice-ul');

  var isMacWebkit = (navigator.userAgent.indexOf("Macintosh") !== -1 &&
    navigator.userAgent.indexOf("WebKit") !== -1);
  var isFirefox = (navigator.userAgent.indexOf("firefox") !== -1);

  element.onwheel = wheelHandler; // Future browsers
  element.onmousewheel = wheelHandler; // Most current browsers
  if (isFirefox) {
    element.scrollTop = 0;
    element.addEventListener("DOMMouseScroll", wheelHandler, false);
  }
  // prevent from scrolling parrent elements
  function wheelHandler(event) {
    var e = event || window.event; // Standard or IE event object

    // Extract the amount of rotation from the event object, looking
    // for properties of a wheel event object, a mousewheel event object 
    // (in both its 2D and 1D forms), and the Firefox DOMMouseScroll event.
    // Scale the deltas so that one "click" toward the screen is 30 pixels.
    // If future browsers fire both "wheel" and "mousewheel" for the same
    // event, we'll end up double-counting it here. Hopefully, however,
    // cancelling the wheel event will prevent generation of mousewheel.
    var deltaX = e.deltaX * -30 || // wheel event
      e.wheelDeltaX / 4 || // mousewheel
      0; // property not defined
    var deltaY = e.deltaY * -30 || // wheel event
      e.wheelDeltaY / 4 || // mousewheel event in Webkit
      (e.wheelDeltaY === undefined && // if there is no 2D property then 
        e.wheelDelta / 4) || // use the 1D wheel property
      e.detail * -10 || // Firefox DOMMouseScroll event
      0; // property not defined

    // Most browsers generate one event with delta 120 per mousewheel click.
    // On Macs, however, the mousewheels seem to be velocity-sensitive and
    // the delta values are often larger multiples of 120, at 
    // least with the Apple Mouse. Use browser-testing to defeat this.
    if (isMacWebkit) {
      deltaX /= 30;
      deltaY /= 30;
    }
    e.currentTarget.scrollTop -= deltaY;
    // If we ever get a mousewheel or wheel event in (a future version of)
    // Firefox, then we don't need DOMMouseScroll anymore.
    if (isFirefox && e.type !== "DOMMouseScroll") {
      element.removeEventListener("DOMMouseScroll", wheelHandler, false);
    }
    // Don't let this event bubble. Prevent any default action.
    // This stops the browser from using the mousewheel event to scroll
    // the document. Hopefully calling preventDefault() on a wheel event
    // will also prevent the generation of a mousewheel event for the
    // same rotation.
    if (e.preventDefault) e.preventDefault();
    if (e.stopPropagation) e.stopPropagation();
    e.cancelBubble = true; // IE events
    e.returnValue = false; // IE events
    return false;
  }

[1] https://dimakuzmich.wordpress.com/2013/07/16/prevent-scrolling-of-parent-element-with-javascript/

[2] http://jsfiddle.net/dima_k/5mPkB/1/

4
jinwei

Cela fonctionne réellement dans AngularJS. Testé sur Chrome et Firefox.

.directive('stopScroll', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            element.bind('mousewheel', function (e) {
                var $this = $(this),
                    scrollTop = this.scrollTop,
                    scrollHeight = this.scrollHeight,
                    height = $this.height(),
                    delta = (e.type == 'DOMMouseScroll' ?
                    e.originalEvent.detail * -40 :
                        e.originalEvent.wheelDelta),
                    up = delta > 0;

                var prevent = function() {
                    e.stopPropagation();
                    e.preventDefault();
                    e.returnValue = false;
                    return false;
                };

                if (!up && -delta > scrollHeight - height - scrollTop) {
                    // Scrolling down, but this will take us past the bottom.
                    $this.scrollTop(scrollHeight);
                    return prevent();
                } else if (up && delta > scrollTop) {
                    // Scrolling up, but this will take us past the top.
                    $this.scrollTop(0);
                    return prevent();
                }
            });
        }
    };
})
4
Remot

la réponse d'amustill en tant que gestionnaire de knock-out:

ko.bindingHandlers.preventParentScroll = {
    init: function (element, valueAccessor, allBindingsAccessor, context) {
        $(element).mousewheel(function (e, d) {
            var t = $(this);
            if (d > 0 && t.scrollTop() === 0) {
                e.preventDefault();
            }
            else {
                if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
                    e.preventDefault();
                }
            }
        });
    }
};
4
mhu

J'ai une situation similaire et voici comment je l'ai résolu:
Tous mes éléments déroulants ont la classe scrollable.

$(document).on('wheel', '.scrollable', function(evt) {
  var offsetTop = this.scrollTop + parseInt(evt.originalEvent.deltaY, 10);
  var offsetBottom = this.scrollHeight - this.getBoundingClientRect().height - offsetTop;

  if (offsetTop < 0 || offsetBottom < 0) {
    evt.preventDefault();
  } else {
    evt.stopImmediatePropagation();
  }
});

stopImmediatePropagation () veille à ne pas faire défiler la zone de défilement parent à partir de la zone enfant à défilement.

Voici une implémentation de JS vanilla: http://jsbin.com/lugim/2/edit?js,output

2
user3259967

Nouveau web dev ici. Cela a fonctionné comme un charme pour moi à la fois sur IE et Chrome.

static preventScrollPropagation(e: HTMLElement) {
    e.onmousewheel = (ev) => {
        var preventScroll = false;
        var isScrollingDown = ev.wheelDelta < 0;
        if (isScrollingDown) {
            var isAtBottom = e.scrollTop + e.clientHeight == e.scrollHeight;
            if (isAtBottom) {
                preventScroll = true;
            }
        } else {
            var isAtTop = e.scrollTop == 0;
            if (isAtTop) {
                preventScroll = true;
            }
        }
        if (preventScroll) {
            ev.preventDefault();
        }
    }
}

Ne laissez pas le nombre de lignes vous tromper, c'est assez simple - juste un peu prolixe pour la lisibilité (code auto-documentant ftw right?)

Je dois également mentionner que le langage utilisé ici est TypeScript , mais comme toujours, il est simple de le convertir en JS.

2
Vivek Maharajh

Départ Leland Kwong code.

L’idée de base est de lier l’événement wheeling à l’élément enfant, puis d’utiliser la propriété javascript native scrollHeight et la propriété jquery outerHeight de l’élément enfant pour détecter la fin du défilement, auquel return false est associé pour empêcher tout défilement.

var scrollableDist,curScrollPos,wheelEvent,dY;
$('#child-element').on('wheel', function(e){
  scrollableDist = $(this)[0].scrollHeight - $(this).outerHeight();
  curScrollPos = $(this).scrollTop();
  wheelEvent = e.originalEvent;
  dY = wheelEvent.deltaY;
  if ((dY>0 && curScrollPos >= scrollableDist) ||
      (dY<0 && curScrollPos <= 0)) {
    return false;
  }
});
1
Vic

mon plugin jQuery:

$('.child').dontScrollParent();

$.fn.dontScrollParent = function()
{
    this.bind('mousewheel DOMMouseScroll',function(e)
    {
        var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;

        if (delta > 0 && $(this).scrollTop() <= 0)
            return false;
        if (delta < 0 && $(this).scrollTop() >= this.scrollHeight - $(this).height())
            return false;

        return true;
    });
}
1
psycho brm

Je l'ai lu dans la bibliothèque choisie: https://github.com/harvesthq/chosen/blob/master/coffee/chosen.jquery.coffee

function preventParentScroll(evt) {
    var delta = evt.deltaY || -evt.wheelDelta || (evt && evt.detail)
    if (delta) {
        evt.preventDefault()
        if (evt.type ==  'DOMMouseScroll') {
            delta = delta * 40  
        }
        fakeTable.scrollTop = delta + fakeTable.scrollTop
    }
}
var el = document.getElementById('some-id')
el.addEventListener('mousewheel', preventParentScroll)
el.addEventListener('DOMMouseScroll', preventParentScroll)

Cela fonctionne pour moi.

1
Drew LeSueur

Pour ceux qui utilisent MooTools, voici le code équivalent:

            'mousewheel': function(event){
            var height = this.getSize().y;
            height -= 2;    // Not sure why I need this bodge
            if ((this.scrollTop === (this.scrollHeight - height) && event.wheel < 0) || 
                (this.scrollTop === 0 && event.wheel > 0)) {
                event.preventDefault();
            }

Gardez à l’esprit que, comme d’autres, je devais modifier une valeur de quelques px, c’est à cela que correspond la hauteur - = 2.

La différence principale réside essentiellement dans le fait que, dans MooTools, les informations sur le delta proviennent de event.wheel au lieu d'un paramètre supplémentaire transmis à l'événement.

En outre, j'avais des problèmes si je liais ce code à quoi que ce soit (event.target.scrollHeight pour une fonction liée n'est pas égal à this.scrollHeight pour un non lié)

J'espère que cela aide quelqu'un autant que ce poste m'a aidé;)

1
Clive Galway

plugin jQuery avec émulation du défilement naturel pour Internet Explorer

  $.fn.mousewheelStopPropagation = function(options) {
    options = $.extend({
        // defaults
        wheelstop: null // Function
        }, options);

    // Compatibilities
    var isMsIE = ('Microsoft Internet Explorer' === navigator.appName);
    var docElt = document.documentElement,
        mousewheelEventName = 'mousewheel';
    if('onmousewheel' in docElt) {
        mousewheelEventName = 'mousewheel';
    } else if('onwheel' in docElt) {
        mousewheelEventName = 'wheel';
    } else if('DOMMouseScroll' in docElt) {
        mousewheelEventName = 'DOMMouseScroll';
    }
    if(!mousewheelEventName) { return this; }

    function mousewheelPrevent(event) {
        event.preventDefault();
        event.stopPropagation();
        if('function' === typeof options.wheelstop) {
            options.wheelstop(event);
        }
    }

    return this.each(function() {
        var _this = this,
            $this = $(_this);
        $this.on(mousewheelEventName, function(event) {
            var origiEvent = event.originalEvent;
            var scrollTop = _this.scrollTop,
                scrollMax = _this.scrollHeight - $this.outerHeight(),
                delta = -origiEvent.wheelDelta;
            if(isNaN(delta)) {
                delta = origiEvent.deltaY;
            }
            var scrollUp = delta < 0;
            if((scrollUp && scrollTop <= 0) || (!scrollUp && scrollTop >= scrollMax)) {
                mousewheelPrevent(event);
            } else if(isMsIE) {
                // Fix Internet Explorer and emulate natural scrolling
                var animOpt = { duration:200, easing:'linear' };
                if(scrollUp && -delta > scrollTop) {
                    $this.stop(true).animate({ scrollTop:0 }, animOpt);
                    mousewheelPrevent(event);
                } else if(!scrollUp && delta > scrollMax - scrollTop) {
                    $this.stop(true).animate({ scrollTop:scrollMax }, animOpt);
                    mousewheelPrevent(event);
                }
            }
        });
    });
};

https://github.com/basselin/jquery-mousewheel-stop-propagation/blob/master/mousewheelStopPropagation.js

0
B.Asselin

Il y a ES 6 navigateur croisé + décision mobile vanilla js:

function stopParentScroll(selector) {
    let last_touch;
    let MouseWheelHandler = (e, selector) => {
        let delta;
        if(e.deltaY)
            delta = e.deltaY;
        else if(e.wheelDelta)
            delta = e.wheelDelta;
        else if(e.changedTouches){
            if(!last_touch){
                last_touch = e.changedTouches[0].clientY;
            }
            else{
                if(e.changedTouches[0].clientY > last_touch){
                    delta = -1;
                }
                else{
                    delta = 1;
                }
            }
        }
        let prevent = function() {
            e.stopPropagation();
            e.preventDefault();
            e.returnValue = false;
            return false;
        };

        if(selector.scrollTop === 0 && delta < 0){
            return prevent();
        }
        else if(selector.scrollTop === (selector.scrollHeight - selector.clientHeight) && delta > 0){
            return prevent();
        }
    };

    selector.onwheel = e => {MouseWheelHandler(e, selector)}; 
    selector.onmousewheel = e => {MouseWheelHandler(e, selector)}; 
    selector.ontouchmove  = e => {MouseWheelHandler(e, selector)};
}
0
Kaifat Kirsan

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

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

Jouez avec la démo ici .

0
Luxiyalu

Il est à noter qu'avec les frameworks modernes tels que reactJS, AngularJS, VueJS, etc., il existe des solutions simples à ce problème lorsqu'il s'agit d'éléments fixes. Des exemples sont les panneaux latéraux ou les éléments superposés.

La technique s'appelle un "portail", ce qui signifie que l'un des composants utilisés dans l'application, sans qu'il soit nécessaire de l'extraire de l'endroit où vous l'utilisez, montera ses enfants au bas de l'élément body, en dehors du parent que vous essayez d'éviter de faire défiler.

Notez que cela n'évitera pas le défilement de l'élément body lui-même. Vous pouvez combiner cette technique et monter votre application dans un div à défilement pour obtenir le résultat attendu.

Exemple d'implémentation de portail dans le matériel de React: https://material-ui-next.com/api/portal/

0
Pandaiolo

Il existe également une astuce amusante pour verrouiller la variable scrollTop du parent lorsque la souris survole un élément déroulant. De cette façon, vous n'avez pas à implémenter votre propre défilement de roue.

Voici un exemple pour empêcher le défilement de document, mais celui-ci peut être ajusté pour n’importe quel élément.

scrollable.mouseenter(function ()
{
  var scroll = $(document).scrollTop();
  $(document).on('scroll.trap', function ()
  {
    if ($(document).scrollTop() != scroll) $(document).scrollTop(scroll);
  });
});

scrollable.mouseleave(function ()
{
  $(document).off('scroll.trap');
});
0
Sebastian Nowak

Je déteste écrire sur necro, mais je cherchais ceci pour MooTools et c’était le premier qui vint . L’exemple original de MooTools fonctionnait avec le défilement vers le haut, mais pas vers le bas, j’ai donc décidé d’écrire celui-ci.


var stopScroll = function (e) {
    var scrollTo = null;
    if (e.event.type === 'mousewheel') {
        scrollTo = (e.event.wheelDelta * -1);
    } else if (e.event.type === 'DOMMouseScroll') {
        scrollTo = 40 * e.event.detail;
    }
    if (scrollTo) {
        e.preventDefault();
        this.scrollTo(0, scrollTo + this.scrollTop);
    }
    return false;
};

Utilisation:

(function)($){
    window.addEvent('domready', function(){
        $$('.scrollable').addEvents({
             'mousewheel': stopScroll,
             'DOMMouseScroll': stopScroll
        });
    });
})(document.id);
0
fyrye

M.K. offert un excellent plugin dans sa réponse. Le plugin peut être trouvé ici . Cependant, par souci d’achèvement, j’ai pensé que ce serait une bonne idée de l’assembler en une seule réponse pour AngularJS.

  1. Commencez par injecter la tonnelle ou le npm (selon votre préférence)

    bower install jquery-scrollLock --save
    npm install jquery-scroll-lock --save
    
  2. Ajoutez la directive suivante. Je choisis de l'ajouter comme attribut

    (function() {
       'use strict';
    
        angular
           .module('app')
           .directive('isolateScrolling', isolateScrolling);
    
           function isolateScrolling() {
               return {
                   restrict: 'A',
                   link: function(sc, elem, attrs) {
                      $('.scroll-container').scrollLock();
                   }
               }
           }
    })();
    
  3. Et la pièce importante que le plug-in ne parvient pas à documenter sur leur site Web est la structure HTML à suivre.

    <div class="scroll-container locked">
        <div class="scrollable" isolate-scrolling>
             ... whatever ...
        </div>
    </div>
    

L'attribut isolate-scrolling doit contenir la classe scrollable et tout doit figurer à l'intérieur de la classe scroll-container ou de la classe de votre choix et la classe locked doit être mise en cascade.

0
LOTUSMS

La meilleure solution que j'ai trouvée consistait à écouter l'événement de défilement dans la fenêtre et à définir le scrollTop sur le précédent scrollTop si la div de l'enfant était visible.

prevScrollPos = 0
$(window).scroll (ev) ->
    if $('#mydiv').is(':visible')
        document.body.scrollTop = prevScrollPos
    else
        prevScrollPos = document.body.scrollTop

Il y a un scintillement dans l'arrière-plan de la div enfant si vous déclenchez beaucoup d'événements de défilement. Cela pourrait donc être modifié, mais cela est à peine remarqué et suffisant pour mon cas d'utilisation.

0
GijsjanB