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

Будь-які ідеї чи кращі способи зробити це?


u встановити тіло як повернути false з коліщатка миші, можливо, це проблема, я припускаю, що ваш контейнер знаходиться всередині тіла праворуч
Рікардо Біннс

@ric_bfa так, але як це виправити
RIK

замість body встановіть контейнер вашого класу / ідентифікатора
Ricardo Binns

Можливо, мені слід пояснити. Коли миша знаходиться всередині елемента .scrolable, здатність тіла прокручувати повинна бути відключена
RIK

4
Чи не існує способу зробити це з усіма CSS і без JavaScript?
chharvey

Відповіді:


165

Оновлення 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 більше не виставляє її у своєму власному об’єкті Події, і замість цього ми повинні отримати її з власного об’єкта Події. .


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), тому ми повинні інвертувати його вручну. Дякую за виправлення моєї відповіді.
Šime Vidas

8
Я цим скористався, дякую! Мені потрібно було додати це:if( e.originalEvent ) e = e.originalEvent;
Ніксо

2
@ ŠimeVidas Я зробив це з урахуванням того, що впровадив його у своє програмне забезпечення. Не критично, але можливо додайте перевірку надійності властивості прокрутки, щоб запобігти тупиковій прокрутці, коли елемент не має прокрутки. 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();

2
Дякую! Я вважаю, що доцільніше не перезаписувати поведінку за замовчуванням. Для крос-браузерної підтримки можна використовувати плагін jQuery Mouse Wheel .
Meettya

На жаль, це здається неприємним у 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.
Syed

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

Я думаю, що краще не перевизначати поведінку прокрутки браузера. Скасовуйте його лише у відповідних випадках.

Це probablt не ідеально xbrowser, але це не може бути дуже важко. Можливо, подвійний напрямок прокрутки Mac є хитрим ...


2
як мені застосувати ту саму функціональність для пристроїв (планшетів / телефонів), я думаю, touchmove є подією прив'язки
ViVekH

6

Використовуйте властивість CSS overscroll-behavior: contain;до дочірнього елемента


Усі інші рішення є надмірними, порівняно з цим власним правилом 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
Іньякі Баз Кастільо

2

Ось моє рішення, яке я використовував у додатках.

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

Потім я створив спільний div (#Prevent) з вищим z-індексом, який охоплює весь веб-сайт. Оскільки #Prevent має вищий z-індекс, він перекриває контейнер веб-сайту. Коли видно #Prevent, миша більше не наводить контейнери веб-сайту, тому прокрутка неможлива.

Звичайно, ви можете розмістити в розмітці інший div, такий як ваш модальний, із z-індексом, вищим ніж #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
Будь ласка, розгляньте інформацію про свою відповідь, а не просто розміщуйте код. Ми намагаємось надавати не просто "виправлення", а допомагати людям вчитися. Ви повинні пояснити, що було неправильно в оригінальному коді, що ви зробили інакше, і чому ваші зміни (зміни) спрацювали.
Andrew Barber

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


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>

Переглянути демонстрацію


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";
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.