Bitte unterbrecht mich, falls ihr das schon mal gehört habt. Man öffnet ein Modal, scrollt darin herum, schließt es und landet an einer anderen Stelle der Seite als dort, wo man das Modal geöffnet hat.
Das liegt daran, dass Modals wie jedes andere Element auf einer Seite sind. Es mag an Ort und Stelle bleiben (vorausgesetzt, das ist seine Funktion), aber der Rest der Seite verhält sich normal.
Siehe den Pen
Vermeide Scrollen des Körpers in Safari, wenn ein Modal-Dialog angezeigt wird von Geoff Graham (@geoffgraham)
auf CodePen.
Manchmal ist das kein Problem, z. B. bei Bildschirmen, die genau die Höhe des Viewports haben. Bei allem anderen sehen wir uns "Scroll City" gegenüber. Die gute Nachricht ist, dass wir das mit einer Prise CSS (und JavaScript) Trickerei verhindern können.
Beginnen wir mit etwas Einfachem
Wir können einen großen Schritt zur Vermeidung des Seiten-Scrollens beim Öffnen eines Modals™ machen, indem wir die Höhe des gesamten Bodys auf die volle Höhe des Viewports setzen und den vertikalen Überlauf ausblenden, wenn das Modal geöffnet ist.
body.modal-open {
height: 100vh;
overflow-y: hidden;
}
Das ist gut und schön, aber wenn wir durch das <body>-Element gescrollt haben, bevor wir das Modal öffnen, bekommen wir ein kleines horizontales Reflow. Die Breite des Viewports wird um etwa 15 Pixel erweitert, was genau die Breite der Scrollleiste ist.
Siehe den Pen
Vermeide Scrollen des Körpers in Safari, wenn ein Modal-Dialog angezeigt wird von Geoff Graham (@geoffgraham)
auf CodePen.
Wir passen den rechten Abstand des Bodys etwas an, um das zu vermeiden.
body {
height: 100vh;
overflow-y: hidden;
padding-right: 15px; /* Avoid width reflow */
}
Beachten Sie, dass das Modal kürzer sein muss als die Höhe des Viewports, damit dies funktioniert. Andernfalls ist die Scrollleiste des Bodys notwendig.
Prima, und was ist mit Mobilgeräten?
Diese Lösung funktioniert sowohl auf Desktops als auch auf Android-Mobilgeräten recht gut. Safari für iOS benötigt jedoch etwas mehr Aufmerksamkeit, da der Body immer noch scrollt, wenn ein Modal geöffnet ist und man auf dem Touchscreen tippt und sich bewegt.
Wir können den Body als Workaround auf eine feste Position setzen.
body {
position: fixed;
}
Funktioniert jetzt! Der Body reagiert nicht mehr, wenn der Bildschirm berührt wird. Es gibt jedoch immer noch ein "kleines" Problem. Nehmen wir an, der Modalauslöser befindet sich weiter unten auf der Seite und wir klicken darauf, um ihn zu öffnen. Prima! Aber jetzt werden wir automatisch wieder zum Anfang des Bildschirms gescrollt, was genauso desorientierend ist wie das Scrollverhalten, das wir zu lösen versuchen.
Siehe den Pen
Vermeide Scrollen des Körpers in Safari, wenn ein Modal-Dialog angezeigt wird von Geoff Graham (@geoffgraham)
auf CodePen.
Bäh!
Deshalb müssen wir zu JavaScript greifen
Wir können JavaScript verwenden, um die Weiterleitung von Touch-Events zu vermeiden. Wir alle wissen, dass es eine Hintergrundebene geben sollte, wenn ein Modal geöffnet ist. Leider ist stopPropagation bei Touch-Events unter iOS etwas umständlich. Aber preventDefault funktioniert gut. Das bedeutet, dass wir Event-Listener auf jedem DOM-Knoten hinzufügen müssen, der im Modal enthalten ist – nicht nur auf der Hintergrundebene oder der Modal-Box-Ebene. Die gute Nachricht ist, dass viele JavaScript-Bibliotheken dies tun können, einschließlich des guten alten jQuery.
Ach ja, und noch etwas: Was, wenn wir *innerhalb* des Modals scrollen müssen? Wir müssen immer noch eine Reaktion auf ein Touch-Event auslösen, aber wenn wir das obere oder untere Ende des Modals erreichen, müssen wir immer noch die Weiterleitung verhindern. Das scheint sehr komplex, wir sind hier also noch nicht ganz aus dem Schneider.
Verbessern wir den Ansatz mit festem Body
Dies ist das, womit wir gearbeitet haben
body {
position: fixed;
}
Wenn wir die obere Scrollposition kennen und sie zu unseren CSS hinzufügen, scrollt der Body nicht zurück zum Anfang des Bildschirms, also ist das Problem gelöst. Wir können dies mit JavaScript tun, indem wir den Scroll-Top berechnen und diesen Wert zu den Body-Stilen hinzufügen.
// When the modal is shown, we want a fixed body
document.body.style.position = 'fixed';
document.body.style.top = `-${window.scrollY}px`;
// When the modal is hidden, we want to remain at the top of the scroll position
document.body.style.position = '';
document.body.style.top = '';
Das funktioniert, aber es gibt immer noch ein kleines Problem nach dem Schließen des Modals. Genauer gesagt, es scheint, dass die Seite ihre Scrollposition bereits verliert, wenn das Modal geöffnet ist und der Body auf fest gesetzt ist. Wir müssen also den Speicherort abrufen. Ändern wir unser JavaScript entsprechend.
// When the modal is hidden...
const scrollY = document.body.style.top;
document.body.style.position = '';
document.body.style.top = '';
window.scrollTo(0, parseInt(scrollY || '0') * -1);
Das war's! Der Body scrollt nicht mehr, wenn ein Modal geöffnet ist, und die Scrollposition wird sowohl bei geöffnetem als auch bei geschlossenem Modal beibehalten. Hurra!
Siehe den Pen
Vermeide Scrollen des Körpers in Safari, wenn ein Modal-Dialog angezeigt wird von Geoff Graham (@geoffgraham)
auf CodePen.
Was ist mit diesem?
Erstellen Sie ein Overlay mit
position: fixed; overflow: auto;, das sich bis zum Fensterrand erstreckt, und legen Sie die Modal-Box in dieses Overlay.Funktioniert leider nicht auf iOS Safari, probieren Sie diese Seite: https://getbootstrap.com/docs/4.3/components/modal/
Keine Erwähnung von overscroll-behavior: contain? Das sollte meiner Meinung nach die Lösung für die Zukunft sein, auch wenn die Unterstützung noch nicht gegeben ist.
Leider verhindert
overscroll-behavior: containnicht das Scrollen, wenn der Inhalt kleiner oder gleich dem Elternelement ist (d. h. kein Überlauf).Wenn das möglich wäre (oder in Browsern behoben?), könnten Sie einfach das Scrollverhalten für das Modal und die Hintergrundebene festlegen und alles wäre großartig und performant.
Sie müssen nur auch
overflow: autofür dasselbe Element festlegen oder eine Reihe von Dingen tun, die dieses Element zu einem Scroll-Container machen.Selbst Ihr "Hurra" scrollt bei mir auf Android Chrome.
Kann ich die Details erfahren?
Danke für das Teilen dieses Artikels, Brad! Wir haben diese Technik auf einigen Websites verwendet und ich wollte schon seit einiger Zeit darüber schreiben, aber jetzt hast du mir die Mühe erspart! Ich bin dir wirklich dankbar dafür.
Seltsamerweise scheint dies auf einigen Projekten mit einem
transform: translateY(...)performanter zu sein, aber es ist nicht konsistent. Manchmal führt das zu einem Flimmern, manchmal ist es merklich schneller. Ich wollte es nur teilen, falls andere davon profitieren, mit der vertikalen Verschiebungseigenschaft zu experimentieren. ♂Ich weiß, dass
transform: translateY(...)position: fixedbei Übergängen unterbricht. Meinst du das?Ehrliche Frage: Können Sie nicht einfach
overflow: hiddenauf demhtml-Element anstelle desbodysetzen und dieses Problem vollständig beseitigen? (abgesehen vom horizontalen Reflow für Scrollbalken)Ich weiß, dass es in Firefox/Chrome/Edge/IE unter Windows funktioniert, habe aber Safari nicht überprüft. Ich wäre nicht überrascht, wenn Safari sich von der Norm abweicht.
Funktioniert nicht auf iOS Safari, kann das Scrollen des Bodys nicht verhindern, wenn das Modal geöffnet ist, auch wenn der Body von einer Hintergrundebene bedeckt ist.
Es scheint, dass Apple hier etwas Seltsames macht, genau wie Safari auf dem Mac
<input type='date'/>nicht unterstützt (unterstützt den Typ, aber nicht das Verhalten), aber Safari unter iOS schon.Es gibt ein sehr leichtes npm-Paket, das dies recht gut handhabt.
https://www.npmjs.com/package/body-scroll-toggle
Es tut im Wesentlichen genau das, was Sie beschrieben haben, behält aber auch die bestehenden Body-Stile bei, bevor es sie ändert.
Scheint großartig. Danke.
Aber was, wenn ich möchte, dass das Modal scrollbar ist, wenn es viel Inhalt enthält?
Oder machen Sie einfach den Modal-Body scrollbar und setzen Sie eine maximale Höhe.
Ich wette, diese 15px Scrollleistenbreite funktioniert nicht browserübergreifend. Sie möchten vielleicht
overflow:scrollverwenden, was eine (immer inaktive, da Sie die Höhe festlegen) Scrollleiste erzwingt, oder etwas basierend auf calc(100vw – 100%) gemäß https://aykevl.nl/2014/09/fix-jumping-scrollbarJa, Sie haben Recht. Ich habe diese
15pxvon Bootstrap gelernt, es funktioniert unter Chrome und Safari.Gut zu sehen, dass einem praktischen Problem Aufmerksamkeit geschenkt wird, das nicht ignoriert werden sollte. Diese Lösung berücksichtigt auch die Safari-Leisten-Größenänderungen und die Geräteausrichtung: https://radogado.github.io/natuive/#modal-window
position: fixed;für dasbody-Element funktioniert bei einfachen Demos gut, hat aber in realen Webseiten auf Mobilgeräten Leistungsprobleme. Das habe ich auf die harte Tour bei der Arbeit an Fancybox gelernt.Es gibt auch das sehr beliebte Body Scroll Lock (BSL) npm-Paket
https://github.com/willmcpo/body-scroll-lock
Ich bin mit Ihrer Annahme, dass eine Scrollleiste überhaupt vorhanden ist, nicht zufrieden. Die Standardeinstellung in macOS ist, dass es einfach keine gibt; zumindest keine, die Platz einnimmt und daher keinen Abstand nach rechts benötigt.
David Walsh hat eine clevere Methode, um herauszufinden, wie viel Abstand benötigt wird!
https://davidwalsh.name/detect-scrollbar-width
Der Schlüssel liegt in
someDiv.offsetWidth, was Ihnen die Breite inklusive vertikaler Scrollbalken gibt. Sie vergleichen dies dann mitsomeDiv.clientWidth, was keine Scrollbalken einschließt, und voilà, Sie wissen, was Sie wissen müssen!Hier ist Davids Code aus seinem Artikel
Safari hat das Problem mit dem scrollenden Body behoben, sodass Sie
position: fixednicht mehr benötigen sollten.Dieser Kommentar hat mir den Tag gerettet. Danke!
In einer schnellen Demo hier
Auf meinem iOS 13.5.1 Telefon scrollt der Body immer noch.
Overscroll-Verhalten ist zwar noch nicht zu 100 % unterstützt, aber im Grunde genau für diesen Anwendungsfall gemacht.
Es verhindert, dass Scrollen innerhalb eines Elements nach oben weitergegeben wird, sobald das Element das Ende seiner Scrollkapazität erreicht hat.
https://caniuse.com/#feat=css-overscroll-behavior
overscroll-behavior: contain, genauer gesagt.Es scheint, dass der Name dieser Variable "scrollY" sein muss und nicht "top"?
const top = document.body.style.top;
https://github.com/willmcpo/body-scroll-lock
Sehr empfehlenswert.
Hallo Brad! Kein "nun ja, eigentlich" von mir – ich wollte nur sagen, danke für diesen hervorragenden Artikel. Dieser Artikel war extrem hilfreich beim Erstellen einer iOS-freundlichen Modal-Komponente.
Ich kam hierher, um das auch zu teilen. Mobile Safari kann echte Scroll-Kopfschmerzen verursachen, besonders wenn man ein Modal mit Scrollen und einen Hintergrund mit Scrollen hat.
Verwandt: https://stackoverflow.com/questions/41594997/ios-10-safari-prevent-scrolling-behind-a-fixed-overlay-and-maintain-scroll-posi
Und deshalb verwende ich keine Hacks... Danke für das Update.
Ich stimme zu,
overscroll-behavior: contain;ist die Antwort auf das Problem. Alles andere scheint ein Hack zu sein.Auch wenn es noch nicht vollständig unterstützt wird und nicht immer greift (andere Kommentare erwähnten Randfälle).
Randfälle benötigen einen Hack für breitere Unterstützung, aber nicht für die optimale Lösung. In ein paar Jahren wird diese Seite veraltet sein und die eigentliche Antwort sollte das erste sein, was gezeigt wird, nicht die Hacks.
Hallo Brad Wu!
Ich benutze Ihren Code und habe mein Problem gelöst. Besonderen Dank. :)
// Wenn das Modal angezeigt wird, wollen wir einen festen Body
document.body.style.position = ‘fixed’;
document.body.style.top =
-${window.scrollY}px;Auf meinem Chrome (OSX) setzt die `position: fixed`-Einstellung den Scroll auf 0 zurück, wenn Sie dieser Reihenfolge folgen. Sie müssen zuerst den Top-Wert setzen und dann die `position: fixed` einstellen, sonst springt Ihr Scroll auf 0, bevor er von `window.scrollY` gelesen wird.
Vielleicht steht das schon in den Kommentaren, aber wenn das Skript aktiviert wird, während der Body bereits fest ist, springt der Scroll zum Anfang der Seite. Das war bei mir der Fall, da ich Navigationsoptionen hatte, die Mega-Dropdowns und dieses Skript auslösten.
Kann mir jemand bei ImgMod helfen: Ich möchte, dass es per Mausrad und nicht per Scrollleiste scrollt, also mit festem Body! Mit den gegebenen Beispielen gelingt mir das nicht. Danke.