iOS Safari - Wie deaktiviere ich Overscroll, erlaube aber scrollbaren Divs, normal zu scrollen?

100

Ich arbeite an einer iPad-basierten Web-App und muss ein Überschreiben verhindern, damit es weniger wie eine Webseite wirkt. Ich verwende dies derzeit, um das Ansichtsfenster einzufrieren und Overscroll zu deaktivieren:

document.body.addEventListener('touchmove',function(e){
      e.preventDefault();
  });

Dies funktioniert hervorragend, um Overscroll zu deaktivieren, aber meine App verfügt über mehrere scrollbare Divs, und der obige Code verhindert, dass sie scrollen .

Ich ziele nur auf iOS 5 und höher ab, um Hacky-Lösungen wie iScroll zu vermeiden. Stattdessen verwende ich dieses CSS für meine scrollbaren Divs:

.scrollable {
    -webkit-overflow-scrolling: touch;
    overflow-y:auto;
}

Dies funktioniert ohne das Dokument-Overscroll-Skript, löst jedoch nicht das Div-Scrolling-Problem.

Gibt es ohne ein jQuery-Plugin eine Möglichkeit, den Overscroll-Fix zu verwenden, aber meine $ ('. Scrollable') Divs auszunehmen?

BEARBEITEN:

Ich habe etwas gefunden, das eine anständige Lösung ist:

 // Disable overscroll / viewport moving on everything but scrollable divs
 $('body').on('touchmove', function (e) {
         if (!$('.scrollable').has($(e.target)).length) e.preventDefault();
 });

Das Ansichtsfenster bewegt sich immer noch, wenn Sie über den Anfang oder das Ende des Divs scrollen. Ich würde gerne einen Weg finden, dies ebenfalls zu deaktivieren.

Jeff
quelle
versuchte dein letztes auch, funktionierte aber auch nicht
Santiago Rebella
Ich konnte verhindern, dass sich das Ansichtsfenster bewegt, wenn Sie über das Ende des Div hinaus scrollen, indem ich das Scroll-Ereignis auf dem übergeordneten Element des scrollbaren Div explizit erfasst und nicht zulasse, dass es tatsächlich scrollt. Wenn Sie jquery mobile verwenden, ist es sinnvoll, dies auf Seitenebene wie folgt zu tun: $ ('div [data-role = "page"]'). On ('scroll', Funktion (e) {e.preventDefault ();});
Christopher Johnson
github.com/lazd/iNoBounce wirkt Wunder
David Underhill
Ich habe dieses Skript gefunden, das dieses Problem behebt! :) github.com/lazd/iNoBounce
Jan Šafránek
Warum sollten Sie den Link erneut veröffentlichen, wenn jemand über Ihrem Beitrag ihn 7 Monate zuvor veröffentlicht hat?
Denny

Antworten:

84

Dies löst das Problem, wenn Sie über den Anfang oder das Ende des Divs scrollen

var selScrollable = '.scrollable';
// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});
// Uses body because jQuery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart', selScrollable, function(e) {
  if (e.currentTarget.scrollTop === 0) {
    e.currentTarget.scrollTop = 1;
  } else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight) {
    e.currentTarget.scrollTop -= 1;
  }
});
// Stops preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove', selScrollable, function(e) {
  e.stopPropagation();
});

Beachten Sie, dass dies nicht funktioniert, wenn Sie das Scrollen ganzer Seiten blockieren möchten, wenn ein Div keinen Überlauf hat. Um dies zu blockieren, verwenden Sie den folgenden Ereignishandler anstelle des unmittelbar oben genannten (angepasst an diese Frage ):

$('body').on('touchmove', selScrollable, function(e) {
    // Only block default if internal div contents are large enough to scroll
    // Warning: scrollHeight support is not universal. (https://stackoverflow.com/a/15033226/40352)
    if($(this)[0].scrollHeight > $(this).innerHeight()) {
        e.stopPropagation();
    }
});
Tyler Dodge
quelle
Dies funktioniert nicht, wenn sich im scrollbaren Bereich ein Iframe befindet und der Benutzer mit dem Scrollen auf diesem Iframe beginnt. Gibt es eine Problemumgehung dafür?
Timo
2
Hat super funktioniert - das ist definitiv besser als nur .scrollabledirektes Targeting (was ich ursprünglich versucht hatte, um dieses Problem zu lösen). Wenn Sie ein JavaScript-Neuling sind und einfachen Code benötigen, um diese Handler irgendwo auf der ganzen Linie zu entfernen, funktionieren diese beiden Zeilen hervorragend für mich! $(document).off('touchmove'); UND $('body').off('touchmove touchstart', '.scrollable');
Devin
Es hat perfekt für mich funktioniert. Vielen Dank, du hast mir Stunden gespart!
Marcgg
1
Dies funktioniert nicht, wenn das div nicht genügend Inhalt zum Scrollen enthält. Jemand stellte eine separate Frage, die diese hier beantwortete: stackoverflow.com/q/16437182/40352
Chris
Wie kann ich mehr als eine ".scrollable" -Klasse zulassen? es funktioniert gut mit einem, aber ich muss auch ein anderes div scrollbar machen. Vielen Dank!
MeV
23

Die exzellente Antwort von Tyler Dodge blieb auf meinem iPad immer wieder zurück, daher habe ich einen Drosselungscode hinzugefügt, der jetzt ziemlich flüssig ist. Beim Scrollen wird manchmal nur minimal übersprungen.

// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});

var scrolling = false;

// Uses body because jquery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart','.scrollable',function(e) {

    // Only execute the below code once at a time
    if (!scrolling) {
        scrolling = true;   
        if (e.currentTarget.scrollTop === 0) {
          e.currentTarget.scrollTop = 1;
        } else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight) {
          e.currentTarget.scrollTop -= 1;
        }
        scrolling = false;
    }
});

// Prevents preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove','.scrollable',function(e) {
  e.stopPropagation();
});

Durch Hinzufügen des folgenden CSS werden außerdem einige Rendering-Fehler ( Quelle ) behoben :

.scrollable {
    overflow: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}
.scrollable * {
    -webkit-transform: translate3d(0,0,0);
}
Kuba Holuj
quelle
Dies funktioniert nicht, wenn sich im scrollbaren Bereich ein Iframe befindet und der Benutzer mit dem Scrollen auf diesem Iframe beginnt. Gibt es eine Problemumgehung dafür?
Timo
1
Scheint perfekt zu funktionieren, um rückwärts zu ziehen, aber wenn man nach unten zieht, bewegt sich die Safari trotzdem.
Abadaba
1
Eine tolle Lösung ... Vielen Dank :)
Aamir Shah
Das hat bei mir funktioniert. Vielen Dank! Ich verbringe mehr als 1,5 Tage damit, dieses Problem zu lösen.
Achintha Samindika
Das ist großartig, hat großartig funktioniert und mir weiteren Stress erspart, eine Lösung zu finden. Danke Kuba!
Leonard
12

Verhindern Sie zunächst wie gewohnt Standardaktionen für Ihr gesamtes Dokument:

$(document).bind('touchmove', function(e){
  e.preventDefault();           
});

Verhindern Sie dann, dass Ihre Elementklasse auf Dokumentebene weitergegeben wird. Dies verhindert, dass die oben genannte Funktion erreicht wird, und daher wird e.preventDefault () nicht initiiert:

$('.scrollable').bind('touchmove', function(e){
  e.stopPropagation();
});

Dieses System scheint natürlicher und weniger intensiv zu sein als die Berechnung der Klasse bei allen Berührungsbewegungen. Verwenden Sie für dynamisch generierte Elemente .on () anstelle von .bind ().

Berücksichtigen Sie auch diese Meta-Tags, um zu verhindern, dass bei der Verwendung Ihres scrollbaren div unglückliche Dinge passieren:

<meta content='True' name='HandheldFriendly' />
<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' name='viewport' />
<meta name="viewport" content="width=device-width" />
Jonathan Tonge
quelle
7

Können Sie Ihrem Overscroll-Deaktivierungscode etwas mehr Logik hinzufügen, um sicherzustellen, dass das betreffende Zielelement nicht das ist, das Sie scrollen möchten? Etwas wie das:

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });
EntwicklerJoe
quelle
3
Danke ... Es scheint, dass dies funktionieren sollte , aber es funktioniert nicht. Sollte es nicht auch "scrollbar" und nicht ".scrollable" (mit dem Punkt) sein?
Jeff
1
Es scheint, dass es das am tiefsten verschachtelte Element ist, das das Berührungsereignis empfängt. Daher müssen Sie möglicherweise alle Ihre Eltern überprüfen, um festzustellen, ob Sie sich in einem scrollbaren Div befinden.
Christopher Johnson
3
Warum sollte man document.body.addEventListener verwenden, wenn jQuery verwendet wird? Ist das aus einem Grund?
Fnagel
7

Die beste Lösung hierfür ist css / html: Erstellen Sie ein Div, in das Sie Ihre Elemente einwickeln können, falls Sie es noch nicht haben, und stellen Sie es so ein, dass es fest und der Überlauf verborgen ist. Optional können Sie Höhe und Breite auf 100% einstellen, wenn der gesamte Bildschirm und nur der gesamte Bildschirm ausgefüllt werden soll

#wrapper{
  height: 100%;
  width: 100%;
  position: fixed;
  overflow: hidden;
}
<div id="wrapper">
  <p>All</p>
  <p>Your</p>
  <p>Elements</p>
</div>

Elias Fyksen
quelle
5

Überprüfen Sie, ob das scrollbare Element beim Scrollen nach oben bereits nach oben oder beim Scrollen nach unten nach unten gescrollt ist, und verhindern Sie dann, dass die Standardaktion verhindert, dass sich die gesamte Seite bewegt.

var touchStartEvent;
$('.scrollable').on({
    touchstart: function(e) {
        touchStartEvent = e;
    },
    touchmove: function(e) {
        if ((e.originalEvent.pageY > touchStartEvent.originalEvent.pageY && this.scrollTop == 0) ||
            (e.originalEvent.pageY < touchStartEvent.originalEvent.pageY && this.scrollTop + this.offsetHeight >= this.scrollHeight))
            e.preventDefault();
    }
});
Johan
quelle
Ich musste nach e.originalEvent.touches [0] .pageY anstelle von e.originalEvent.pageY suchen. Es hat funktioniert, aber nur, wenn Sie bereits am Ende des Bildlauf-Divs sind. Wenn der Bildlauf ausgeführt wird (z. B. wenn Sie sehr schnell gescrollt haben), stoppt er nicht, sobald das Ende des scrollbaren Div erreicht ist.
Keen Sage
4

Ich habe nach einer Möglichkeit gesucht, das Scrollen des gesamten Körpers zu verhindern, wenn ein Popup mit einem scrollbaren Bereich angezeigt wird (ein Popup "Einkaufswagen" mit einer scrollbaren Ansicht Ihres Warenkorbs).

Ich habe eine weitaus elegantere Lösung mit minimalem Javascript geschrieben, um die Klasse "noscroll" auf Ihrem Körper einfach umzuschalten, wenn Sie ein Popup oder div haben, das Sie scrollen möchten (und nicht den gesamten Seitenkörper "überrollen").

Während Desktop-Browser einen Überlauf beobachten: versteckt - iOS scheint dies zu ignorieren, es sei denn, Sie setzen die Position auf fest ... was dazu führt, dass die gesamte Seite eine seltsame Breite hat, sodass Sie die Position und Breite auch manuell einstellen müssen. benutze dieses CSS:

.noscroll {
    overflow: hidden;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
}

und diese Frage:

/* fade in/out cart popup, add/remove .noscroll from body */
$('a.cart').click(function() {
    $('nav > ul.cart').fadeToggle(100, 'linear');
    if ($('nav > ul.cart').is(":visible")) {
        $('body').toggleClass('noscroll');
    } else {
        $('body').removeClass('noscroll');
    }
});

/* close all popup menus when you click the page... */
$('body').click(function () {
    $('nav > ul').fadeOut(100, 'linear');
    $('body').removeClass('noscroll');
});

/* ... but prevent clicks in the popup from closing the popup */
$('nav > ul').click(function(event){
    event.stopPropagation();
});
nrutas
quelle
Dies ist sehr hilfreich und ein minimaler Ansatz, genau das, was ich brauchte. Setzen der Position auf fest, mit oben: 0; links: 0; Breite: 100%; waren die Elemente, die mir fehlten. Dies ist auch nützlich für Flyout-Menüs.
Bdanin
3

Ich habe ein wenig Workarround ohne Abfrage gearbeitet. Nicht perfert, funktioniert aber gut (besonders wenn Sie ein Scroll-X in einem Scoll-Y haben) https://github.com/pinadesign/overscroll/

Fiel frei, um teilzunehmen und es zu verbessern

PiñaDesign
quelle
1
Hatte das gleiche Problem wie Jeff, habe jede Antwort ausprobiert, deine hat funktioniert. Danke dir!
Dominik Schreiber
Die akzeptierte Antwort funktionierte nur für mich, wenn das div mit .scrollable genug Inhalt hatte, um einen Überlauf zu verursachen. Wenn es nicht überlief, bestand der "Bounce" -Effekt immer noch. Dies funktioniert jedoch perfekt, danke!
Adam Marshall
1

Diese Lösung erfordert nicht, dass Sie eine scrollbare Klasse auf alle Ihre scrollbaren Divs setzen, ist also allgemeiner. Das Scrollen ist für alle Elemente zulässig, die INPUT-Elemente contenteditables und Überlauf-Scrolling oder Autos sind oder untergeordnete Elemente sind.

Ich verwende einen benutzerdefinierten Selektor und speichere auch das Ergebnis der Prüfung im Element zwischen, um die Leistung zu verbessern. Sie müssen nicht jedes Mal dasselbe Element überprüfen. Dies kann ein paar Probleme haben, wie gerade geschrieben, aber dachte, ich würde teilen.

$.expr[':'].scrollable = function(obj) {
    var $el = $(obj);
    var tagName = $el.prop("tagName");
    return (tagName !== 'BODY' && tagName !== 'HTML') && (tagName === 'INPUT' || $el.is("[contentEditable='true']") || $el.css("overflow").match(/auto|scroll/));
};
function preventBodyScroll() {
    function isScrollAllowed($target) {
        if ($target.data("isScrollAllowed") !== undefined) {
            return $target.data("isScrollAllowed");
        }
        var scrollAllowed = $target.closest(":scrollable").length > 0;
        $target.data("isScrollAllowed",scrollAllowed);
        return scrollAllowed;
    }
    $('body').bind('touchmove', function (ev) {
        if (!isScrollAllowed($(ev.target))) {
            ev.preventDefault();
        }
    });
}
jcbdrn
quelle
1

Das Deaktivieren aller "touchmove" -Ereignisse scheint eine gute Idee zu sein. Sobald Sie andere scrollbare Elemente auf der Seite benötigen, kann dies zu Problemen führen. Wenn Sie außerdem "touchmove" -Ereignisse nur für bestimmte Elemente deaktivieren (z. B. body, wenn die Seite nicht scrollbar sein soll), führt IOS bei der URL zu einer unaufhaltsamen Weitergabe in Chrome, sobald sie an einer anderen Stelle aktiviert wird Bar schaltet um.

Obwohl ich dieses Verhalten nicht erklären kann, scheint es der einzige Weg zu sein, dies zu verhindern, die Position des Körpers zu bestimmen fixed. Das einzige Problem dabei ist, dass Sie die Position des Dokuments verlieren - dies ist beispielsweise bei Modalen besonders ärgerlich. Eine Möglichkeit, dies zu lösen, besteht darin, diese einfachen VanillaJS-Funktionen zu verwenden:

function disableDocumentScrolling() {
    if (document.documentElement.style.position != 'fixed') {
        // Get the top vertical offset.
        var topVerticalOffset = (typeof window.pageYOffset != 'undefined') ?
            window.pageYOffset : (document.documentElement.scrollTop ? 
            document.documentElement.scrollTop : 0);
        // Set the document to fixed position (this is the only way around IOS' overscroll "feature").
        document.documentElement.style.position = 'fixed';
        // Set back the offset position by user negative margin on the fixed document.
        document.documentElement.style.marginTop = '-' + topVerticalOffset + 'px';
    }
}

function enableDocumentScrolling() {
    if (document.documentElement.style.position == 'fixed') {
        // Remove the fixed position on the document.
        document.documentElement.style.position = null;
        // Calculate back the original position of the non-fixed document.
        var scrollPosition = -1 * parseFloat(document.documentElement.style.marginTop);
        // Remove fixed document negative margin.
        document.documentElement.style.marginTop = null;
        // Scroll to the original position of the non-fixed document.
        window.scrollTo(0, scrollPosition);
    }
}

Mit dieser Lösung können Sie ein festes Dokument haben und jedes andere Element auf Ihrer Seite kann mithilfe von einfachem CSS (z overflow: scroll;. B. ) überlaufen . Keine Notwendigkeit für spezielle Klassen oder irgendetwas anderes.

Nicolas Bouvrette
quelle
0

Hier ist eine zeptokompatible Lösung

    if (!$(e.target).hasClass('scrollable') && !$(e.target).closest('.scrollable').length > 0) {
       console.log('prevented scroll');
       e.preventDefault();
       window.scroll(0,0);
       return false;
    }
Christopher Johnson
quelle
0

Dieser funktioniert für mich (einfaches Javascript)

var fixScroll = function (className, border) {  // className = class of scrollElement(s), border: borderTop + borderBottom, due to offsetHeight
var reg = new RegExp(className,"i"); var off = +border + 1;
function _testClass(e) { var o = e.target; while (!reg.test(o.className)) if (!o || o==document) return false; else o = o.parentNode; return o;}
document.ontouchmove  = function(e) { var o = _testClass(e); if (o) { e.stopPropagation(); if (o.scrollTop == 0) { o.scrollTop += 1; e.preventDefault();}}}
document.ontouchstart = function(e) { var o = _testClass(e); if (o && o.scrollHeight >= o.scrollTop + o.offsetHeight - off) o.scrollTop -= off;}
}

fixScroll("fixscroll",2); // assuming I have a 1px border in my DIV

html:

<div class="fixscroll" style="border:1px gray solid">content</div>
RJS
quelle
0

Versuchen Sie dies. Es wird perfekt funktionieren.

$('body.overflow-hidden').delegate('#skrollr-body','touchmove',function(e){
    e.preventDefault();
    console.log('Stop skrollrbody');
}).delegate('.mfp-auto-cursor .mfp-content','touchmove',function(e){
    e.stopPropagation();
    console.log('Scroll scroll');
});
Srinivasan Thirupathi
quelle
0

Ich hatte überraschendes Glück mit einfach:

body {
    height: 100vh;
}

Es funktioniert hervorragend, um das Überschreiben für Popups oder Menüs zu deaktivieren, und es erzwingt nicht, dass Browserleisten angezeigt werden, wenn Position: fest verwendet wird. ABER - Sie müssen die Bildlaufposition speichern, bevor Sie die feste Höhe einstellen, und sie wiederherstellen, wenn Sie das Popup ausblenden. Andernfalls wird der Browser nach oben scrollen.

Láďa Durchánek
quelle