Как предотвратить прокрутку страницы при прокрутке элемента DIV?

Я рассмотрел и протестировал различные функции для предотвращения способности тела прокручиваться, пока он находится внутри элемента, и объединил функцию, которая должна работать.

$('.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;
    });
});
  • Это останавливает всю прокрутку, где, как я хочу, прокрутка все еще возможна внутри контейнера
  • Это также не деактивирует при отпускании мыши

Есть идеи или лучшие способы сделать это?

12 ответов

Решение

Обновление 2: Мое решение основано на отключении полной прокрутки браузера (когда курсор находится внутри DIV), а затем вручную прокручивает DIV с помощью JavaScript (путем установки его .scrollTop имущество). Альтернативным и лучшим подходом IMO будет выборочное отключение прокрутки браузера, чтобы предотвратить прокрутку страницы, но не прокрутку DIV. Проверьте ответ Руди ниже, который демонстрирует это решение.


Ну вот:

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

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

Демонстрационная версия: https://jsbin.com/howojuq/edit?js,output

Таким образом, вы вручную устанавливаете положение прокрутки, а затем просто предотвращаете поведение по умолчанию (которое будет прокручивать DIV или всю веб-страницу).

Обновление 1: Как отметил Крис в комментариях ниже, в более новых версиях jQuery дельта-информация вкладывается в .originalEvent объект, то есть jQuery больше не предоставляет его в своем пользовательском объекте Event, и мы должны вместо этого извлечь его из собственного объекта Event.

Если вас не заботит совместимость со старыми версиями IE (< 8), вы можете создать собственный плагин jQuery и затем вызвать его для переполненного элемента.

Это решение имеет преимущество перед предложенным Шиме Видасом, так как оно не перезаписывает режим прокрутки - оно просто блокирует его при необходимости.

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

Используйте свойство ниже CSS overscroll-behavior: contain; к дочернему элементу

Я думаю, что иногда можно отменить событие 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();
});

Внутри обработчика событий вам нужно знать:

  • направление прокрутки
    d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down'потому что положительное число означает прокрутку вниз
  • положение прокрутки
    scrollTop для верха, scrollHeight - scrollTop - offsetHeight для дна

Если вы

  • прокручивая вверх, и top = 0, или же
  • прокручивая вниз, и bottom = 0,

отменить мероприятие: e.preventDefault() (а может даже e.stopPropagation()).

Я думаю, что лучше не игнорировать поведение прокрутки браузера. Отмените его только тогда, когда это применимо.

Вероятно, это не совсем хорошо, но это не может быть очень сложно. Может быть, двойное направление прокрутки Mac сложно, хотя...

Посмотрим, поможет ли это вам:

демо: jsfiddle

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

редактировать:

попробуй это:

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

В приведенном выше решении есть небольшая ошибка в отношении Firefox. В Firefox событие "DOMMouseScroll" не имеет свойства e.detail, чтобы получить это свойство, необходимо написать следующее "e.originalEvent.detail".

Вот рабочее решение для 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;
};

Вот простое решение без jQuery, которое не разрушает встроенную прокрутку браузера (это: нет искусственной / некрасивой прокрутки):

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

Демонстрация в реальном времени: http://jsfiddle.net/ibcaliax/bwmzfmq7/4/

На мой взгляд, менее удачное решение - установить переполнение, скрытое на теле, когда вы наводите курсор мыши на прокручиваемый элемент div. Это предотвратит прокрутку тела, но возникнет нежелательный эффект "прыжка". Следующее решение работает вокруг этого:

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

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

Вот мое решение, которое я использовал в приложениях.

Я отключил переполнение тела и поместил весь веб-сайт html в div контейнера. Контейнеры веб-сайта переполнены, и поэтому пользователь может прокручивать страницу, как и ожидалось.

Затем я создал элемент div (#Prevent) с более высоким z-индексом, который охватывает весь сайт. Поскольку #Prevent имеет более высокий z-индекс, он перекрывает контейнер веб-сайта. Когда виден #Prevent, мышь больше не наведена на контейнеры веб-сайта, поэтому прокрутка невозможна.

Конечно, вы можете поместить в разметку другой div, например, ваш модальный, с более высоким z-index, чем #Prevent. Это позволяет создавать всплывающие окна, которые не страдают от проблем с прокруткой.

Это решение лучше, потому что оно не скрывает полосы прокрутки (эффект прыжка). Он не требует прослушивателей событий и его легко реализовать. Он работает во всех браузерах, хотя с IE7 и 8 вам придется поиграться (зависит от вашего конкретного кода).

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

отключить / включить прокрутку

PreventScroll('on'); // prevent scrolling
PreventScroll('off'); // allow scrolling

Мне нужно было добавить это событие в несколько элементов, которые могут иметь полосу прокрутки. В тех случаях, когда полоса прокрутки отсутствовала, главная полоса прокрутки не работала так, как должна. Поэтому я внес небольшое изменение в код @Šime следующим образом:

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

Теперь только элементы с полосой прокрутки будут препятствовать началу основной прокрутки.

Вы можете сделать это без JavaScript. Вы можете установить стиль на обоих div position: fixed а также overflow-y: auto, Возможно, вам придется сделать один из них выше другого, установив его z-index (если они перекрываются).

Вот основной пример на CodePen.

Все что тебе нужно это

e.preventDefault();

на дочернем элементе.

Вот плагин, который полезен для предотвращения родительской прокрутки при прокрутке определенного div и имеет множество опций для игры.

Проверьте это здесь:

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

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

Триггер Scroll Lock через JavaScript:

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

Триггер Scroll Lock через разметку:

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

Посмотреть демо

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

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

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

Чистая версия ответа Vidas на JavaScript, el$ это DOM-узел плоскости, которую вы прокручиваете.

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

Убедитесь, что вы не прикрепили даже несколько раз! Вот пример,

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

Предостережение: функция должна быть константой, если вы переинициализируете функцию каждый раз перед ее подключением, т.е. var func = function (...) removeEventListener не будет работать.

Другие вопросы по тегам