Предотвратить прокрутку родительского элемента, когда позиция прокрутки внутреннего элемента достигает верха/низа?

У меня есть небольшая "плавающая панель инструментов" - div с position:fixed; overflow:auto. Работает нормально.

Но когда прокручивается внутри этого окна (с колесом мыши) и достигает нижней вершины ИЛИ, родительский элемент "берет на себя" запрос "прокрутки": документ за окном окна инструментов.
- Что раздражает, а не то, что пользователь "просил".

Я использую jQuery и думал, что могу остановить это поведение с event.stoppropagation():
$("#toolBox").scroll( function(event){ event.stoppropagation() });

Он вводит функцию, но все равно распространение происходит в любом случае (прокрутка документа)
- На удивление трудно найти эту тему на SO (и Google), поэтому я должен спросить:
Как предотвратить распространение/барботирование события scroll?

Edit:
Рабочее решение благодаря амустиллу (и Брэндон Аарон для плагина mousewheel-плагин здесь:
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();
        }
    }
});

Ответ 2

Я добавляю этот ответ для полноты, потому что принятый ответ by @amustill неправильно решает проблему в Internet Explorer. Подробности см. В моем исходном сообщении. Кроме того, это решение не требует никаких плагинов - только jQuery.

В сущности, код работает, обрабатывая событие mousewheel. Каждое такое событие содержит wheelDelta, равное числу px, которое будет перемещать прокручиваемую область. Если это значение >0, мы прокручиваем up. Если wheelDelta - <0, тогда мы прокручиваем down.

FireFox: FireFox использует DOMMouseScroll в качестве события и заполняет originalEvent.detail, чей +/- отменяется от того, что описано выше. Он обычно возвращает интервалы 3, в то время как другие браузеры возвращают прокрутку с интервалом 120 (по крайней мере, на моей машине). Чтобы исправить это, мы просто обнаруживаем его и умножаем на -40 для нормализации.

@amustill answer работает, отменив событие, если прокручиваемая область <div> уже находится в верхней или нижней максимальной позиции. Однако Internet Explorer игнорирует отмененное событие в ситуациях, когда delta больше, чем оставшееся прокручиваемое пространство.

Другими словами, если у вас есть 200px tall <div>, содержащий 500px прокручиваемого содержимого, а текущий scrollTop - это 400, событие mousewheel, которое сообщает браузеру прокручивать 120px далее приведет к прокрутке <div> и <body>, потому что 400 + 120 > 500.

Итак - для решения проблемы нам нужно сделать что-то немного другое, как показано ниже:

Необходимым кодом jQuery является:

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

В сущности, этот код отменяет любое событие прокрутки, которое создавало бы нежелательное краевое условие, а затем использует jQuery для установки scrollTop <div> на максимальное или минимальное значение, в зависимости от того, в каком направлении mousewheel событие запрашивало.

Поскольку событие полностью отменяется в любом случае, оно никогда не распространяется на body вообще, и поэтому решает проблему в IE, а также во всех других браузерах.

Я также создал рабочий стол на jsFiddle.

Ответ 3

Я знаю, это довольно старый вопрос, но поскольку это один из лучших результатов в google... Мне пришлось как-то отменить прокрутку пузырьков без jQuery, и этот код работает для меня:

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

Ответ 4

РЕДАКТИРОВАТЬ: пример CodePen

Для AngularJS я определил следующую директиву:

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

И затем добавил его в прокручиваемый элемент (выпадающее меню 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>

Проверено на Chrome и Firefox. Хромкая плавная прокрутка побеждает этот хак, когда большое движение колесика мыши происходит вблизи (но не в) верхней или нижней части области прокрутки.

Ответ 5

Есть много вопросов, подобных этому, с множеством ответов, но я не смог найти удовлетворительное решение, которое не включало события, скрипты, плагины и т.д. Я хотел сохранить его прямо в HTML и CSS. Я наконец нашел решение, которое сработало, хотя оно связано с реструктуризацией разметки, чтобы разбить цепочку событий.


1. Основная проблема

Вход прокрутки (то есть: колесико мыши), примененный к модальному элементу, будет перетекать в элемент предка и прокручивать его в том же направлении, если какой-либо такой элемент прокручивается:

(Все примеры предназначены для просмотра на разрешениях на рабочем столе)

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. Нет родительского прокрутки в модальном прокрутке

Причина, по которой прокрутка предка заканчивается, заключается в том, что пузырьки прокрутки и некоторый элемент в цепочке способны справиться с этим. Способ остановить это - убедиться, что ни один из элементов в цепочке не знает, как обращаться с прокруткой. В нашем примере мы можем реорганизовать дерево для перемещения модального элемента родительского элемента. По неясным причинам недостаточно держать родителя и модных родственников DOM; родитель должен быть обернут другим элементом, который устанавливает новый контекст стекирования. Совершенно позиционированная обертка вокруг родителя может сделать трюк.

В результате мы получим, что до тех пор, пока модальный принимает событие прокрутки, событие не будет пузыриться к "родительскому" элементу.

Как правило, возможно редизайн дерева DOM для поддержки этого поведения, не влияя на то, что видит конечный пользователь.

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 (только новый):

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

3. Никакой прокрутки нигде, кроме модального, пока он вверх

Решение выше все еще позволяет родителям получать события прокрутки, если они не перехватываются модальным окном (т.е. если он запускается с помощью мыши, а курсор не находится над модальным). Иногда это нежелательно, и мы можем запретить все фоновые прокрутки, пока модальная версия. Для этого нам нужно вставить дополнительный контекст стекирования, который охватывает весь видовой экран за модальным. Мы можем сделать это, показывая абсолютно позиционированное оверлей, которое может быть полностью прозрачным, если необходимо (но не 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 (новый поверх # 2):

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

Ответ 6

Angular Директива JS

Мне пришлось обернуть директиву angular. Ниже приводится Mashup других ответов здесь. в Chrome и 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;
            }
        }
    }
});

Использование

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

Надеюсь, это поможет следующему человеку, который попадает сюда из поиска Google.

Ответ 7

В качестве варианта, чтобы избежать проблем с производительностью при обработке scroll или mousewheel, вы можете использовать код, как показано ниже:

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

Пример работы: http://jsbin.com/damuwinarata/4

Ответ 8

Во всех решениях, приведенных в этой теме, не упоминается о существующем и родном способах решения этой проблемы без переупорядочения DOM и/или использования приемов предотвращения событий. Но есть веская причина: этот способ проприетарный - и доступен только на веб-платформе MS. Цитирование MSDN:

-ms-scroll-chaining свойство - определяет поведение прокрутки, которое происходит, когда пользователь достигает предела прокрутки во время манипуляции. Значения свойства:

прикованный - начальное значение. Ближайший прокручиваемый родительский элемент начинает прокрутку, когда пользователь достигает предела прокрутки во время манипуляции. Эффект отказов не отображается.

none - эффект отскока отображается, когда пользователь достигает предела прокрутки во время манипуляции.

Конечно, это свойство поддерживается только для IE10+/Edge. Тем не менее, здесь есть цитата:

Чтобы дать вам представление о том, насколько популярным может быть предотвращение создания цепочек прокрутки, согласно моему быстрому поиску в http-архиве "-ms-scroll-chaining: none" используется в 0,4% верхних страниц по 300 КБ, несмотря на то, что он ограничен в функциональности и поддерживается только IE/Край.

А теперь хорошие новости всем! Начиная с Chrome 63, у нас наконец-то есть собственное лекарство для платформ на основе Blink - и Chrome (очевидно), и Android WebView (скоро).

Цитирую вводную статью:

Свойство overscroll-поведения - это новая функция CSS, которая управляет поведением того, что происходит, когда вы перебираете контейнер (включая саму страницу). Вы можете использовать его для отмены цепочки прокрутки, отключения/настройки действия "тянуть-обновить", отключения эффектов резиновой ленты на iOS (когда Safari реализует поведение overscroll-поведения) и т.д. [...]

Свойство принимает три возможных значения:

авто - по умолчанию. Свитки, которые происходят на элементе, могут распространяться на элементы-предки.

содержать - предотвращает связывание прокрутки. Свитки не распространяются на предков, но показаны локальные эффекты внутри узла. Например, эффект свечения при прокрутке на Android или эффект резиновой ленты на iOS, который уведомляет пользователя, когда он достиг границы прокрутки. Примечание: использование overscroll-поведения: Содержать в элементе html предотвращает действия навигации с избыточной прокруткой.

нет - то же самое, что и содержащиеся, но также предотвращает эффекты избыточной прокрутки внутри самого узла (например, свечение избыточной прокрутки Android или резиновая полоса iOS)

[...] Самое приятное то, что использование overscroll-поведения не оказывает негативного влияния на производительность страницы, как взломы, упомянутые во вступлении!

Вот эта особенность в действии. А вот соответствующий документ модуля CSS.

ОБНОВЛЕНИЕ: Firefox, начиная с версии 59, вступил в клуб, и MS Edge, как ожидается, реализует эту функцию в версии 18. Здесь соответствующий caniusage.

Ответ 9

Использование свойств прокрутки собственного элемента с дельта-значением из плагина 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();
    }
});

Ответ 10

Если кто-то все еще ищет решение для этого, следующий плагин выполняет работу http://mohammadyounes.github.io/jquery-scrollLock/

Он полностью решает проблему блокировки прокрутки колесика мыши внутри данного контейнера, предотвращая его распространение на родительский элемент.

Он не меняет скорость прокрутки колес, не влияет на пользователя. и вы получаете такое же поведение, независимо от скорости прокрутки мыши мыши мыши (в Windows его можно установить на один экран или одну строку до 100 строк на метку).

Демо: http://mohammadyounes.github.io/jquery-scrollLock/example/

Источник: https://github.com/MohammadYounes/jquery-scrollLock

Ответ 11

Вот простая версия JavaScript:

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

Ответ 12

Ответ на вопрос в качестве обработчика нокаута:

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

Ответ 13

Это действительно работает в AngularJS. Протестировано в Chrome и 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();
                }
            });
        }
    };
})

Ответ 14

метод выше не является естественным, после некоторого поиска в Google я нахожу более приятное решение и не нужно jQuery. см. [1] и демонстрацию [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;
  }

Ответ 15

Для тех, кто использует MooTools, здесь приведен эквивалентный код:

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

Имейте в виду, что я, как и некоторые другие, должен был настроить значение на пару пикселей, то есть для высоты = = 2.

В основном основное отличие заключается в том, что в MooTools информация о дельте поступает из event.wheel вместо дополнительного параметра, переданного в событие.

Кроме того, у меня были проблемы, если я привязывал этот код к чему-либо (event.target.scrollHeight для связанной функции не равно this.scrollHeight для несвязанного)

Надеюсь, это поможет кому-то так же, как эта почта помогла мне;)

Ответ 16

мой плагин 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;
    });
}

Ответ 17

Новый веб-разработчик здесь. Это работало как прелесть для меня как в IE, так и в 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();
        }
    }
}

Не позволяйте количеству строк обмануть вас, это довольно просто - просто немного подробный для читаемости (самодокументирующий код ftw справа?)

Также следует упомянуть, что язык здесь TypeScript, но, как всегда, его можно преобразовать в JS.

Ответ 18

Я выбрал это из выбранной библиотеки: 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)

Это работает для меня.

Ответ 19

Я ненавижу сообщение о некроре, но я искал это для MooTools, и это было первое, что появилось. Первоначальный пример MooTools будет работать с прокруткой вверх, но не прокруткой вниз, поэтому я решил написать это.


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

Применение:

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

Ответ 20

Плагин jQuery с эмуляцией естественной прокрутки для 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

Ответ 21

Лучшее решение, которое я смог найти, это прослушивание события прокрутки в окне и установка scrollTop в предыдущий scrollTop, если дочерний div был виден.

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

На заднем фоне дочернего div есть мерцание, если вы запускаете много событий прокрутки, поэтому это можно было бы изменить, но это вряд ли можно заметить, и этого было достаточно для моего использования.

Ответ 22

У меня такая же ситуация, и вот как я ее решил:
Все мои прокручиваемые элементы получают класс прокручиваемый.

$(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() не позволяет прокручивать родительскую прокручиваемую область из прокручиваемой дочерней области.

Вот его реализация: http://jsbin.com/lugim/2/edit?js,output

Ответ 23

Не используйте overflow: hidden; в body. Он автоматически прокручивает все до вершины. Также нет необходимости в JavaScript. Используйте overflow: auto;:

Структура HTML

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

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

Стайлинг

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

Играйте с демо здесь.

Ответ 24

Отметьте код Leland Kwong.

Основная идея - связать событие вращения с дочерним элементом, а затем использовать собственное свойство javascript scrollHeight и свойство jquery outerHeight дочернего элемента для обнаружения конца прокрутки, на котором return false к событию вращения, чтобы предотвратить прокрутку.

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

Ответ 25

Там также забавный трюк, чтобы заблокировать родительский scrollTop, когда мышь нависает над прокручиваемым элементом. Таким образом, вам не нужно выполнять собственную прокрутку колес.

Здесь приведен пример предотвращения прокрутки документа, но его можно настроить для любого элемента.

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

Ответ 26

M.K. предложил отличный плагин в своем ответе. Плагин можно найти здесь. Однако, ради завершения, я подумал, что было бы неплохо собрать его в один ответ для AngularJS.

  • Начните с инъекции беседки или npm (в зависимости от того, что предпочтительнее)

    bower install jquery-scrollLock --save
    npm install jquery-scroll-lock --save
    
  • Добавьте следующую директиву. Я хочу добавить его как атрибут

    (function() {
       'use strict';
    
        angular
           .module('app')
           .directive('isolateScrolling', isolateScrolling);
    
           function isolateScrolling() {
               return {
                   restrict: 'A',
                   link: function(sc, elem, attrs) {
                      $('.scroll-container').scrollLock();
                   }
               }
           }
    })();
    
  • И важной частью, которую плагин не может документировать на своем веб-сайте, является структура HTML, которой он должен следовать.

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

Атрибут isolate-scrolling должен содержать класс scrollable, и все это должно быть внутри класса scroll-container или любого выбранного вами класса, а класс locked должен быть каскадным.

Ответ 27

Стоит отметить, что в современных средах, таких как реагировать на JJ, AngularJS, VueJS и т.д., Есть простые решения этой проблемы при работе с элементами с фиксированным положением. Примерами являются боковые панели или накладные элементы.

Этот метод называется "Портал", что означает, что один из компонентов, используемых в приложении, без необходимости фактически извлекать его из того места, где вы его используете, будет монтировать его дочерние элементы в нижней части элемента тела, вне родитель, которого вы пытаетесь избежать прокрутки.

Обратите внимание, что это не помешает прокрутке самого элемента body. Вы можете комбинировать эту технику и монтировать свое приложение в прокручивающем элементе для достижения ожидаемого результата.

Пример реализации портала в React material-ui: https://material-ui-next.com/api/portal/

Ответ 28

Есть кроссбраузер ES 6 + мобильное решение vanila 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)};
}

Ответ 29

Простое решение с событием mouseweel:

$('.element').bind('mousewheel', function(e, d) {
    console.log(this.scrollTop,this.scrollHeight,this.offsetHeight,d);
    if((this.scrollTop === (this.scrollHeight - this.offsetHeight) && d < 0)
        || (this.scrollTop === 0 && d > 0)) {
        e.preventDefault();
    }
});

Ответ 30

Вы можете попробовать это следующим образом:

$('#element').on('shown', function(){ 
   $('body').css('overflow-y', 'hidden');
   $('body').css('margin-left', '-17px');
});

$('#element').on('hide', function(){ 
   $('body').css('overflow-y', 'scroll');
   $('body').css('margin-left', '0px');
});