Jak zapobiec przewijaniu strony podczas przewijania elementu DIV?

94

Przejrzałem i przetestowałem różne funkcje zapobiegające przewijaniu treści wewnątrz elementu div i połączyłem funkcję, która powinna działać.

$('.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;
    });
});
  • Zatrzymuje to przewijanie w miejscu, w którym chcę, aby przewijanie nadal było możliwe wewnątrz kontenera
  • Nie oznacza to również dezaktywacji po opuszczeniu myszy

Jakieś pomysły lub lepsze sposoby na zrobienie tego?

RIK
źródło
ustawiłeś ciało jako powrót fałszywe z kółka myszy, może to jest problem, przypuszczam, że twój pojemnik jest w porządku
Ricardo Binns
@ric_bfa tak, ale jak to naprawić
RIK
zamiast body, ustaw kontener klasy / id
Ricardo Binns
Może powinienem to wyjaśnić. Kiedy mysz znajduje się wewnątrz elementu „.scrollable”, możliwość przewijania treści powinna zostać wyłączona
RIK
4
Czy nie ma sposobu, aby to zrobić ze wszystkimi arkuszami CSS i bez JavaScript?
chharvey

Odpowiedzi:

166

Aktualizacja 2: Moje rozwiązanie polega na całkowitym wyłączeniu natywnego przewijania przeglądarki (gdy kursor znajduje się wewnątrz DIV), a następnie ręcznym przewijaniu DIV za pomocą JavaScript (poprzez ustawienie jego .scrollTopwłaściwości). Alternatywnym i lepszym podejściem IMO byłoby tylko selektywne wyłączenie przewijania przeglądarki, aby zapobiec przewijaniu strony, ale nie przewijaniu DIV. Sprawdź odpowiedź Rudiego poniżej, która demonstruje to rozwiązanie.


Proszę bardzo:

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

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

Demo na żywo: https://jsbin.com/howojuq/edit?js,output

Więc ręcznie ustawiasz pozycję przewijania, a następnie po prostu zapobiegasz domyślnemu zachowaniu (którym byłoby przewijanie DIV lub całej strony internetowej).

Aktualizacja 1: Jak zauważył Chris w komentarzach poniżej, w nowszych wersjach jQuery informacje o różnicach są zagnieżdżone w .originalEventobiekcie, tj. JQuery nie uwidacznia ich już w swoim niestandardowym obiekcie Event i zamiast tego musimy je pobrać z natywnego obiektu Event .

Šime Vidas
źródło
2
@RobinKnight Nie, nie wydaje się. Oto działające demo: jsfiddle.net/XNwbt/1, a tutaj jest demo z usuniętymi liniami: jsfiddle.net/XNwbt/2
Šime Vidas
1
Twoje ręczne przewijanie odbywa się wstecz w przeglądarce Firefox 10, przynajmniej w systemie Linux i Mac. Wydaje się działać poprawnie, jeśli to zrobisz -e.detail, przetestowano w Firefoksie (Mac, Linux), Safari (Mac) i Chromium (Linux).
Anomie,
1
@Anomie Zgadza się. Znak Mozilli .detailnie odpowiada znakowi .wheelData(który ma Chrome / IE), więc musimy go ręcznie odwrócić. Dzięki za poprawienie mojej odpowiedzi.
Šime Vidas,
8
Użyłem tego, dziękuję! Musiałem to dodać:if( e.originalEvent ) e = e.originalEvent;
Nikso
2
@ ŠimeVidas Wprowadziłem poprawki po tym, jak zaimplementowałem je w swoim oprogramowaniu. Nie krytyczne, ale prawdopodobnie dodaj kontrolę poprawności dla właściwości scroll, aby zapobiec przewijaniu zakleszczenia, gdy element nie ma przewijania. if ( $(this)[0].scrollHeight !== $(this).outerHeight() ) { //yourcode }będzie wykonywany tylko wtedy, gdy jest pasek przewijania.
user0000001
30

Jeśli nie zależy Ci na kompatybilności ze starszymi wersjami IE (<8), możesz stworzyć niestandardową wtyczkę jQuery, a następnie wywołać ją w przepełnionym elemencie.

To rozwiązanie ma przewagę nad rozwiązaniem zaproponowanym przez Šime Vidas, ponieważ nie zastępuje zachowania przewijania - po prostu blokuje je w razie potrzeby.

$.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
źródło
2
Dzięki! Myślę, że lepiej nie nadpisywać domyślnego zachowania. Do obsługi wielu przeglądarek można użyć wtyczki jQuery Mouse Wheel .
Meettya
Niestety wydaje się to kłopotliwe w Chrome 52 (OSX 10.10). Jednak działa doskonale w Safari.
mroźny
1
Dziękuję Ci! Inne rozwiązania sprawiły, że przewijanie było bardzo wolne i buggy
agrublev
@wojcikstefan, w chrome otrzymuję to ostrzeżenie:[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

Myślę, że czasami można anulować wydarzenie 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();
});

W programie obsługi zdarzeń musisz wiedzieć:

  • kierunek przewijania,
    d = e.originalEvent.deltaY, dir = d < 0 ? 'up' : 'down' ponieważ liczba dodatnia oznacza przewijanie w dół
  • pozycja przewijania
    scrollTopna górze, scrollHeight - scrollTop - offsetHeightna dole

Jeśli jesteś

  • przewijanie w górę i top = 0, lub
  • przewijania, a bottom = 0,

odwołać wydarzenie: e.preventDefault()(a może nawet e.stopPropagation()).

Myślę, że lepiej nie zastępować przewijania przeglądarki. Anuluj ją tylko wtedy, gdy ma to zastosowanie.

Prawdopodobnie nie jest to doskonale przeglądarka internetowa, ale nie może być bardzo trudne. Może jednak podwójny kierunek przewijania Maca jest trudny ...

Rudie
źródło
2
jak zaimplementować tę samą funkcjonalność na urządzeniach (tabletach / telefonach), myślę, że touchmove jest wiążącym wydarzeniem
ViVekH
11

Użyj poniższej właściwości CSS overscroll-behavior: contain;do elementu podrzędnego

Prasoon Kumar
źródło
Wszystkie inne rozwiązania są przesadą w porównaniu z tą natywną regułą CSS. Ładnie wykonane.
TechWisdom
3

zobacz, czy to ci pomoże:

demo: jsfiddle

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

edytować:

Spróbuj tego:

    $("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;
           });
       }
    });
Ricardo Binns
źródło
Twoje elementy są oddzielne, podczas gdy jeden z moich jest zawarty w drugim.
RIK,
3

Mniej zmyślnym rozwiązaniem, moim zdaniem, jest ustawienie przepełnienia ukrytego na ciele po najechaniu kursorem myszy na przewijalny element div. Zapobiegnie to przewijaniu treści, ale pojawi się niepożądany efekt „skakania”. Następujące rozwiązanie pozwala to obejść:

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

W razie potrzeby możesz uczynić swój kod mądrzejszym. Na przykład możesz sprawdzić, czy treść ma już wypełnienie, a jeśli tak, dodaj do tego nową wyściółkę.

Alexandru Lighezan
źródło
3

W powyższym rozwiązaniu jest mały błąd dotyczący Firefoksa. W przeglądarce Firefox zdarzenie „DOMMouseScroll” nie ma właściwości e.detail, aby uzyskać tę właściwość, należy wpisać „e.originalEvent.detail”.

Oto działające rozwiązanie dla przeglądarki 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;
};
Vladimir Kuzmin
źródło
Działa to całkiem dobrze, z wyjątkiem sytuacji, gdy zderzysz się z górną lub dolną częścią div, które pierwsze zdarzenie przenosi do ciała. Testowanie za pomocą dotyku 2 palcami na chromie w systemie OSX
johnb003
3

tutaj proste rozwiązanie bez jQuery, które nie niszczy natywnego przewijania przeglądarki (to jest: brak sztucznego / brzydkiego przewijania):

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

Demo na żywo: http://jsfiddle.net/ibcaliax/bwmzfmq7/4/

Iñaki Baz Castillo
źródło
Właśnie opublikowałem bibliotekę NPM implementującą powyższy kod: npmjs.com/package/dontscrollthebody
Iñaki Baz Castillo
2

Oto moje rozwiązanie, którego używałem w aplikacjach.

Wyłączyłem przepełnienie treści i umieściłem cały html witryny wewnątrz kontenera div. Kontenery witryny są przepełnione, dlatego użytkownik może przewijać stronę zgodnie z oczekiwaniami.

Następnie utworzyłem rodzeństwo div (#Prevent) z wyższym indeksem Z, który obejmuje całą witrynę. Ponieważ #Prevent ma wyższy indeks Z, nakłada się na kontener witryny. Kiedy #Prevent jest widoczne, mysz nie jest już najeżdżana na kontenery witryny, więc przewijanie nie jest możliwe.

Możesz oczywiście umieścić inny element div, na przykład modalny, z indeksem Z wyższym niż #Prevent w znaczniku. Pozwala to na tworzenie wyskakujących okienek, które nie cierpią z powodu problemów z przewijaniem.

To rozwiązanie jest lepsze, ponieważ nie ukrywa pasków przewijania (wpływa na przeskok). Nie wymaga detektorów zdarzeń i jest łatwy do wdrożenia. Działa we wszystkich przeglądarkach, chociaż z IE7 i 8 musisz się bawić (w zależności od konkretnego kodu).

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

wyłącz / włącz przewijanie

PreventScroll('on'); // prevent scrolling
PreventScroll('off'); // allow scrolling
David D.
źródło
2
Zastanów się nad dołączeniem niektórych informacji o swojej odpowiedzi, zamiast po prostu opublikować kod. Staramy się dostarczać nie tylko „poprawki”, ale pomagamy ludziom się uczyć. Powinieneś wyjaśnić, co było nie tak w oryginalnym kodzie, co zrobiłeś inaczej i dlaczego Twoja zmiana (e) zadziałała.
Andrew Barber
1

Musiałem dodać to zdarzenie do wielu elementów, które mogą mieć pasek przewijania. W przypadkach, gdy nie było paska przewijania, główny pasek przewijania nie działał tak, jak powinien. Więc zrobiłem małą zmianę w kodzie @ Šime w następujący sposób:

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

Teraz tylko elementy z paskiem przewijania zapobiegną zatrzymaniu głównego przewijania.

Ricky
źródło
0

Czysta wersja javascript odpowiedzi Vidasa el$to węzeł DOM samolotu, który przewijasz.

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

Upewnij się, że nie dołączasz nawet wielokrotnie! Oto przykład,

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

Uwaga , funkcja tam musi być stała, jeśli za każdym razem inicjalizujesz funkcję przed jej dołączeniem, tj. nie będzie działać.var func = function (...)removeEventListener

srcspider
źródło
0

Możesz to zrobić bez JavaScript. Możesz ustawić styl obu elementów div na position: fixedi overflow-y: auto. Być może trzeba będzie ustawić jeden z nich wyżej od drugiego, ustawiając jego z-index(jeśli nachodzą na siebie).

Oto podstawowy przykład dotyczący CodePen .

Carl Smith
źródło
0

Oto wtyczka, która jest przydatna do zapobiegania przewijaniu przez rodzica podczas przewijania określonego elementu div i ma wiele opcji do zabawy.

Sprawdź to tutaj:

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

Stosowanie

Uruchom blokadę przewijania przez JavaScript:

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

Uruchom blokadę przewijania za pomocą znaczników:

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

Zobacz demo

MR_AMDEV
źródło
0

oferując to jako możliwe rozwiązanie, jeśli nie sądzisz, że użytkownik będzie miał negatywne doświadczenia związane z oczywistą zmianą. Po prostu zmieniłem klasę przepełnienia ciała na ukrytą, gdy mysz znajdowała się nad docelowym elementem div; potem zmieniłem element div ciała na ukryty przepełnienie, gdy mysz wychodzi.

Osobiście nie uważam, że wygląda to źle, mój kod mógłby użyć przełącznika, aby uczynić go czystszym, a są oczywiste korzyści z umożliwienia tego efektu bez świadomości użytkownika. Więc to jest prawdopodobnie ostatnia deska ratunku.

//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
źródło
0

Wszystko czego potrzebujesz to

e.preventDefault();

na elemencie potomnym.

Ayberk Baytok
źródło