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

94

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

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

RIK
источник
Вы установили тело как return false от колесика мыши, возможно, это проблема, я полагаю, ваш контейнер находится внутри тела справа
Рикардо Биннс
@ric_bfa да, но как это исправить
RIK
вместо body установите контейнер class / id
Рикардо Биннс,
Возможно, мне следует уточнить. Когда мышь находится внутри элемента .scrollable, способность тела прокручиваться должна быть деактивирована
RIK
4
Разве нет способа сделать это со всем CSS и без JavaScript?
chharvey

Ответы:

166

Обновление 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. .

Шиме Видас
источник
2
@RobinKnight Нет, не похоже. Вот рабочая демонстрация: jsfiddle.net/XNwbt/1 и вот демонстрация с удаленными этими строками: jsfiddle.net/XNwbt/2
Шиме Видас
1
В Firefox 10 ручная прокрутка выполняется в обратном направлении, по крайней мере, в Linux и Mac. Кажется, работает правильно, если вы это сделаете -e.detail, протестировано в Firefox (Mac, Linux), Safari (Mac) и Chromium (Linux).
Anomie
1
@Anomie Это верно. Знак Mozilla .detailне соответствует знаку .wheelData(который есть в Chrome / IE), поэтому мы должны инвертировать его вручную. Спасибо, что исправили мой ответ.
Шиме Видас
8
Я использовал это, спасибо! Мне нужно было добавить это:if( e.originalEvent ) e = e.originalEvent;
Nikso
2
@ ŠimeVidas Я изменил это после того, как внедрил в свое программное обеспечение. Не критично, но, возможно, добавьте проверку работоспособности для свойства scroll, чтобы предотвратить тупиковую прокрутку, когда элемент не имеет прокрутки. if ( $(this)[0].scrollHeight !== $(this).outerHeight() ) { //yourcode }будет выполняться только при наличии полосы прокрутки.
user0000001
30

Если вас не волнует совместимость со старыми версиями 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();
Wojcikstefan
источник
2
Благодарность! Я думаю, что лучше не перезаписывать поведение по умолчанию. Для кроссбраузерной поддержки можно использовать плагин jQuery Mouse Wheel .
Meettya 02
К сожалению, в Chrome 52 (OSX 10.10) это кажется сбой. Тем не менее, отлично работает в Safari.
морозный
1
Спасибо! Другие решения сделали прокрутку
сверхмедленной
@wojcikstefan, я получаю это предупреждение в хроме:[Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' event. Consider marking event handler as 'passive' to make the page more responsive.
Сайед,
21

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

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

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

Руди
источник
2
Как мне реализовать ту же функциональность для устройств (планшетов / телефонов), я полагаю, touchmove - это событие привязки
ViVekH
12

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

Prasoon Kumar
источник
Все остальные решения излишни по сравнению с этим собственным правилом CSS. Отлично сделано.
TechWisdom
3

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

демонстрация: 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;
           });
       }
    });
Рикардо Биннс
источник
Ваши элементы отделены друг от друга, а один из моих содержится в другом.
RIK
3

На мой взгляд, менее хакерским решением является установка переполнения, скрытого на теле, когда вы наводите курсор мыши на прокручиваемый 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"
        });
    })

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

Александру Лигезан
источник
3

В приведенном выше решении есть небольшая ошибка относительно 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;
};
Владимир Кузьмин
источник
Это работает очень хорошо, за исключением случаев, когда вы врезаетесь в верхнюю или нижнюю часть div, первое событие переносится в тело. Тестирование двумя пальцами касания хрома в osx.
johnb003
3

вот простое решение без 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/

Иньяки Баз Кастильо
источник
Я только что опубликовал библиотеку NPM, реализующую приведенный выше код: npmjs.com/package/dontscrollthebody
Iñaki Baz Castillo
2

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

Я отключил переполнение тела и поместил весь 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
Дэвид Д.
источник
2
Пожалуйста, подумайте о том, чтобы включить некоторую информацию о своем ответе, а не просто размещать код. Мы стараемся не просто «исправлять», но и помогать людям учиться. Вы должны объяснить, что было не так в исходном коде, что вы сделали иначе и почему ваши изменения сработали.
Эндрю Барбер
1

Мне нужно было добавить это событие к нескольким элементам, которые могли иметь полосу прокрутки. В случаях, когда полоса прокрутки отсутствовала, основная полоса прокрутки работала не так, как должна. Поэтому я внес небольшое изменение в код @ Š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();
    }       
});

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

Рики
источник
0

Версия ответа Видаса на чистом 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

srcspider
источник
0

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

Вот базовый пример на CodePen .

Карл Смит
источник
0

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

Посмотрите здесь:

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

Применение

Запуск блокировки прокрутки через JavaScript:

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

Запуск блокировки прокрутки с помощью разметки:

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

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

MR_AMDEV
источник
0

просто предлагайте это как возможное решение, если вы не думаете, что у пользователя будет негативный опыт очевидного изменения. Я просто изменил класс переполнения тела на скрытый, когда указатель мыши находился над целевым 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";
}
Thuperman
источник
0

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

e.preventDefault();

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

Айберк Байток
источник