Safari in ios8 scrollt den Bildschirm, wenn feste Elemente den Fokus erhalten

96

In IOS8 Safari gibt es einen neuen Fehler, bei dem die Position behoben wurde.

Wenn Sie einen Textbereich fokussieren, der sich in einem festen Bereich befindet, werden Sie durch Safari zum Ende der Seite gescrollt.

Dies macht es unmöglich, mit allen Arten von Benutzeroberflächen zu arbeiten, da Sie keine Möglichkeit haben, Text in Textbereiche einzugeben, ohne Ihre Seite ganz nach unten zu scrollen und Ihren Platz zu verlieren.

Gibt es eine Möglichkeit, diesen Fehler sauber zu umgehen?

#a {
  height: 10000px;
  background: linear-gradient(red, blue);
}
#b {
  position: fixed;
  bottom: 20px;
  left: 10%;
  width: 100%;
  height: 300px;
}

textarea {
   width: 80%;
   height: 300px;
}
<html>
   <body>
   <div id="a"></div>
   <div id="b"><textarea></textarea></div>
   </body>
</html>
Sam Safran
quelle
1
Würde es helfen, einen Z-Index für #b festzulegen?
Ben
1
Der z-Index ist keine Hilfe, vielleicht würde eine ausgefallene No-CSS-Transformation viel mit Stapelkontexten zu tun haben, nicht sicher.
Sam Saffron
1
Für den Kontext hier ist die Diskussion über den Diskurs: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/…
Sam Saffron
79
iOS Safari ist der neue IE
geedubb
4
@geedubb stimmte zu. Jedes schwachsinnige Betriebssystem, das seine Standard-Browserversion an das Betriebssystem bindet, wird den Problemen, die den Internet Explorer in den letzten 7 Jahren geplagt haben, zum Opfer fallen.
Tau

Antworten:

58

Basierend auf dieser guten Analyse dieses Problems habe ich dies in htmlund bodyElemente in CSS verwendet:

html,body{
    -webkit-overflow-scrolling : touch !important;
    overflow: auto !important;
    height: 100% !important;
}

Ich denke, es funktioniert großartig für mich.

Mohammad AlBanna
quelle
2
arbeitete auch für mich. Dies hat viele andere Dinge durcheinander gebracht, da ich mein DOM unter Last manipuliere. Deshalb mache ich dies zu einer Klasse und füge es dem HTML-Body hinzu, nachdem sich das DOM stabilisiert hat. Dinge wie scrollTop funktionieren nicht sehr gut (ich mache automatisches Scrollen), aber auch hier können Sie die Klasse hinzufügen / entfernen, während Sie scrollen. Schlechte Arbeit eines Teils des Safari-Teams.
Amarsh
1
Leute, die diese Option betrachten, möchten möglicherweise auch transform: translateZ(0);von stackoverflow.com/questions/7808110/…
lkraav
1
Dies löst das Problem, aber wenn Sie Animationen haben, sehen diese sehr abgehackt aus. Könnte besser sein, es in eine Medienabfrage zu verpacken.
mmla
Arbeitete für mich unter iOS 10.3.
QuotesBro
Es löst das Problem nicht. Sie müssen das Scrollen abfangen, wenn die virtuelle Tastatur angezeigt wird
Brian Haak
36

Die beste Lösung, die ich finden könnte, besteht darin, position: absolute;auf Fokus zu wechseln und die Position zu berechnen, an der es sich befand, als es verwendet wurde position: fixed;. Der Trick ist, dass das focusEreignis zu spät ausgelöst wird und daher touchstartverwendet werden muss.

Die Lösung in dieser Antwort ahmt das korrekte Verhalten in iOS 7 sehr genau nach.

Bedarf:

Das bodyElement muss positioniert sein, um eine ordnungsgemäße Positionierung zu gewährleisten, wenn das Element auf absolute Positionierung umschaltet.

body {
    position: relative;
}

Der Code ( Live-Beispiel ):

Der folgende Code ist ein grundlegendes Beispiel für den bereitgestellten Testfall und kann an Ihren spezifischen Anwendungsfall angepasst werden.

//Get the fixed element, and the input element it contains.
var fixed_el = document.getElementById('b');
var input_el = document.querySelector('textarea');
//Listen for touchstart, focus will fire too late.
input_el.addEventListener('touchstart', function() {
    //If using a non-px value, you will have to get clever, or just use 0 and live with the temporary jump.
    var bottom = parseFloat(window.getComputedStyle(fixed_el).bottom);
    //Switch to position absolute.
    fixed_el.style.position = 'absolute';
    fixed_el.style.bottom = (document.height - (window.scrollY + window.innerHeight) + bottom) + 'px';
    //Switch back when focus is lost.
    function blured() {
        fixed_el.style.position = '';
        fixed_el.style.bottom = '';
        input_el.removeEventListener('blur', blured);
    }
    input_el.addEventListener('blur', blured);
});

Hier ist der gleiche Code ohne den Hack zum Vergleich .

Vorbehalt:

Wenn das position: fixed;Element andere übergeordnete Elemente mit Positionierung enthält body, kann das Umschalten auf position: absolute;ein unerwartetes Verhalten aufweisen. Aufgrund der Natur ist position: fixed;dies wahrscheinlich kein großes Problem, da das Verschachteln solcher Elemente nicht üblich ist.

Empfehlungen:

Während die Verwendung des touchstartEreignisses die meisten Desktop-Umgebungen herausfiltert, möchten Sie wahrscheinlich User-Agent-Sniffing verwenden, damit dieser Code nur für das defekte iOS 8 und nicht für andere Geräte wie Android und ältere iOS-Versionen ausgeführt wird. Leider wissen wir noch nicht, wann Apple dieses Problem in iOS beheben wird, aber ich wäre überrascht, wenn es in der nächsten Hauptversion nicht behoben wird.

Alexander O'Mara
quelle
Ich frage mich, ob eine doppelte Umhüllung mit einem Div und die Einstellung der Höhe auf% 100 für ein transparentes Umhüllungs-Div dazu beitragen könnte, dies zu vermeiden ...
Sam Saffron
@SamSaffron Können Sie klären, wie eine solche Technik funktionieren könnte? Ich habe ein paar solche Dinge ohne Erfolg versucht. Da die Höhe des Dokuments nicht eindeutig ist, bin ich mir nicht sicher, wie es funktionieren könnte.
Alexander O'Mara
Ich dachte, einfach eine "feste" Verpackung mit 100% Höhe zu haben, könnte dies umgehen, möglicherweise nicht
Sam Saffron
@downvoter: Habe ich etwas falsch gemacht? Ich stimme zu, dass dies eine schreckliche Lösung ist, aber ich glaube nicht, dass es bessere gibt.
Alexander O'Mara
4
Das hat bei mir nicht funktioniert, das Eingabefeld bewegt sich immer noch.
Rodrigo Ruiz
8

Ich habe eine Methode gefunden, die funktioniert, ohne dass die Position absolut geändert werden muss!

Vollständiger unkommentierter Code

var scrollPos = $(document).scrollTop();
$(window).scroll(function(){
    scrollPos = $(document).scrollTop();
});
var savedScrollPos = scrollPos;

function is_iOS() {
  var iDevices = [
    'iPad Simulator',
    'iPhone Simulator',
    'iPod Simulator',
    'iPad',
    'iPhone',
    'iPod'
  ];
  while (iDevices.length) {
    if (navigator.platform === iDevices.pop()){ return true; }
  }
  return false;
}

$('input[type=text]').on('touchstart', function(){
    if (is_iOS()){
        savedScrollPos = scrollPos;
        $('body').css({
            position: 'relative',
            top: -scrollPos
        });
        $('html').css('overflow','hidden');
    }
})
.blur(function(){
    if (is_iOS()){
        $('body, html').removeAttr('style');
        $(document).scrollTop(savedScrollPos);
    }
});

Brechen sie ab

Zuerst muss das feste Eingabefeld im HTML-Code oben auf der Seite stehen (es ist ein festes Element, daher sollte es semantisch sinnvoll sein, es ohnehin oben zu haben):

<!DOCTYPE HTML>

<html>

    <head>
      <title>Untitled</title>
    </head>

    <body>
        <form class="fixed-element">
            <input class="thing-causing-the-issue" type="text" />
        </form>

        <div class="everything-else">(content)</div>

    </body>

</html>

Dann müssen Sie die aktuelle Bildlaufposition in globalen Variablen speichern:

//Always know the current scroll position
var scrollPos = $(document).scrollTop();
$(window).scroll(function(){
    scrollPos = $(document).scrollTop();
});

//need to be able to save current scroll pos while keeping actual scroll pos up to date
var savedScrollPos = scrollPos;

Dann benötigen Sie eine Möglichkeit, iOS-Geräte zu erkennen, damit sie keine Auswirkungen auf Dinge haben, für die keine Korrektur erforderlich ist (Funktion von https://stackoverflow.com/a/9039885/1611058 ).

//function for testing if it is an iOS device
function is_iOS() {
  var iDevices = [
    'iPad Simulator',
    'iPhone Simulator',
    'iPod Simulator',
    'iPad',
    'iPhone',
    'iPod'
  ];

  while (iDevices.length) {
    if (navigator.platform === iDevices.pop()){ return true; }
  }

  return false;
}

Jetzt, wo wir alles haben, was wir brauchen, ist hier das Update :)

//when user touches the input
$('input[type=text]').on('touchstart', function(){

    //only fire code if it's an iOS device
    if (is_iOS()){

        //set savedScrollPos to the current scroll position
        savedScrollPos = scrollPos;

        //shift the body up a number of pixels equal to the current scroll position
        $('body').css({
            position: 'relative',
            top: -scrollPos
        });

        //Hide all content outside of the top of the visible area
        //this essentially chops off the body at the position you are scrolled to so the browser can't scroll up any higher
        $('html').css('overflow','hidden');
    }
})

//when the user is done and removes focus from the input field
.blur(function(){

    //checks if it is an iOS device
    if (is_iOS()){

        //Removes the custom styling from the body and html attribute
        $('body, html').removeAttr('style');

        //instantly scrolls the page back down to where you were when you clicked on input field
        $(document).scrollTop(savedScrollPos);
    }
});
Daniel Tonon
quelle
+1. Dies ist eine wesentlich weniger komplizierte Lösung als die akzeptierte Antwort, wenn Sie eine nicht triviale DOM-Hierarchie haben. Dies sollte mehr positive Stimmen haben
Anson Kao
Könnten Sie dies auch in nativem JS bereitstellen? Vielen Dank!
Mesqueeb
@ SamSaffron, hat diese Antwort wirklich für dich funktioniert? Kann ich hier ein Beispiel haben? es hat bei mir nicht funktioniert?
Ganesh Putta
@ SamSaffron, ist diese Antwort wirklich Ihr Problem gelöst, können Sie ein Beispiel senden, das für Sie funktioniert hat, arbeite an der gleichen, aber es hat nicht für mich funktioniert.
Ganesh Putta
@ GaneshPutta Es ist möglich, dass ein neueres iOS-Update dazu geführt hat, dass dies nicht mehr funktioniert. Ich habe das vor 2,5 Jahren gepostet. Es sollte trotzdem funktionieren, wenn Sie alle Anweisungen genau befolgt haben: /
Daniel Tonon
4

Ich konnte dies für ausgewählte Eingaben beheben, indem ich den erforderlichen Auswahlelementen einen Ereignis-Listener hinzufügte und dann um einen Versatz von einem Pixel scrollte, wenn die betreffende Auswahl den Fokus erhielt.

Dies ist nicht unbedingt eine gute Lösung, aber viel einfacher und zuverlässiger als die anderen Antworten, die ich hier gesehen habe. Der Browser scheint die Position neu zu rendern / neu zu berechnen: behoben; Attribut basierend auf dem in der Funktion window.scrollBy () angegebenen Offset.

document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
user3411121
quelle
2

Ähnlich wie Mark Ryan Sallee vorgeschlagen hat, stellte ich fest, dass das dynamische Ändern der Höhe und des Überlaufs meines Hintergrundelements der Schlüssel ist - dies gibt Safari nichts, zu dem man scrollen kann.

Ändern Sie nach Abschluss der Eröffnungsanimation des Modals das Styling des Hintergrunds:

$('body > #your-background-element').css({
  'overflow': 'hidden',
  'height': 0
});

Wenn Sie das Modal schließen, ändern Sie es zurück:

$('body > #your-background-element').css({
  'overflow': 'auto',
  'height': 'auto'
});

Während andere Antworten in einfacheren Kontexten nützlich sind, war mein DOM (dank SharePoint) zu kompliziert, um den absoluten / festen Positionswechsel zu verwenden.

Matthew Levy
quelle
1

Sauber? Nein.

Ich hatte dieses Problem kürzlich selbst mit einem festen Suchfeld in einer klebrigen Kopfzeile. Das Beste, was Sie im Moment tun können, ist, die Bildlaufposition jederzeit in einer Variablen zu halten und bei der Auswahl die Position des festen Elements absolut anstatt mit einem oberen zu fixieren Position basierend auf der Bildlaufposition des Dokuments.

Dies ist jedoch sehr hässlich und führt immer noch zu einem seltsamen Hin- und Her-Scrollen, bevor ich an der richtigen Stelle lande, aber es ist das nächste, das ich bekommen könnte.

Bei jeder anderen Lösung wird die Standard-Bildlaufmechanik des Browsers überschrieben.

Samuel
quelle
1

Dies ist jetzt in iOS 10.3 behoben!

Hacks sollten nicht mehr benötigt werden.

Sam Safran
quelle
1
Können Sie auf Versionshinweise verweisen, die darauf hinweisen, dass dies behoben ist?
Bluepnume
Apple ist sehr geheim, sie haben meinen Fehlerbericht geschlossen. Ich habe bestätigt, dass es jetzt richtig funktioniert, das ist alles, was ich habe :)
Sam Saffron
Ich habe immer noch dieses Problem auf iOS 11
zekia
0

Ich habe mich nicht mit diesem speziellen Fehler befasst, aber vielleicht einen Überlauf gesetzt: versteckt; auf dem Körper, wenn der Textbereich sichtbar ist (oder nur aktiv ist, abhängig von Ihrem Design). Dies kann dazu führen, dass der Browser nirgendwo "unten" zum Scrollen angezeigt wird.

Mark Ryan Sallee
quelle
1
Ich kann nicht einmal scheinen, Touchstart früh genug auszulösen, um diesen Hack überhaupt in Betracht zu ziehen :(
Sam Saffron
0

Eine mögliche Lösung wäre, das Eingabefeld zu ersetzen.

  • Überwachen Sie Klickereignisse auf einem Div
  • Fokussieren Sie ein verstecktes Eingabefeld, um die Tastatur zu rendern
  • Replizieren Sie den Inhalt des ausgeblendeten Eingabefelds in das gefälschte Eingabefeld

function focus() {
  $('#hiddeninput').focus();
}

$(document.body).load(focus);

$('.fakeinput').bind("click",function() {
    focus();
});

$("#hiddeninput").bind("keyup blur", function (){
  $('.fakeinput .placeholder').html(this.value);
});
#hiddeninput {
  position:fixed;
  top:0;left:-100vw;
  opacity:0;
  height:0px;
  width:0;
}
#hiddeninput:focus{
  outline:none;
}
.fakeinput {
  width:80vw;
  margin:15px auto;
  height:38px;
  border:1px solid #000;
  color:#000;
  font-size:18px;
  padding:12px 15px 10px;
  display:block;
  overflow:hidden;
}
.placeholder {
  opacity:0.6;
  vertical-align:middle;
}
<input type="text" id="hiddeninput"></input>

<div class="fakeinput">
    <span class="placeholder">First Name</span>
</div> 


Codepen

Davidcondrey
quelle
0

Keine dieser Lösungen hat bei mir funktioniert, da mein DOM kompliziert ist und ich dynamische unendliche Bildlaufseiten habe, sodass ich meine eigenen erstellen musste.

Hintergrund: Ich verwende einen festen Header und ein Element weiter unten, das darunter haftet, sobald der Benutzer so weit nach unten scrollt. Dieses Element verfügt über ein Sucheingabefeld. Außerdem wurden beim Vorwärts- und Rückwärtslauf dynamische Seiten hinzugefügt.

Problem: Unter iOS scrollte der Browser jedes Mal, wenn der Benutzer auf die Eingabe im festen Element klickte, ganz nach oben. Dies verursachte nicht nur unerwünschtes Verhalten, sondern löste auch das Hinzufügen meiner dynamischen Seite oben auf der Seite aus.

Erwartete Lösung: Kein Bildlauf in iOS (überhaupt kein Bildlauf), wenn der Benutzer auf die Eingabe im Sticky-Element klickt.

Lösung:

     /*Returns a function, that, as long as it continues to be invoked, will not
    be triggered. The function will be called after it stops being called for
    N milliseconds. If `immediate` is passed, trigger the function on the
    leading edge, instead of the trailing.*/
    function debounce(func, wait, immediate) {
        var timeout;
        return function () {
            var context = this, args = arguments;
            var later = function () {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };

     function is_iOS() {
        var iDevices = [
          'iPad Simulator',
          'iPhone Simulator',
          'iPod Simulator',
          'iPad',
          'iPhone',
          'iPod'
        ];
        while (iDevices.length) {
            if (navigator.platform === iDevices.pop()) { return true; }
        }
        return false;
    }

    $(document).on("scrollstop", debounce(function () {
        //console.log("Stopped scrolling!");
        if (is_iOS()) {
            var yScrollPos = $(document).scrollTop();
            if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
                $('#searchBarDiv').css('position', 'absolute');
                $('#searchBarDiv').css('top', yScrollPos + 50 + 'px'); //50 for fixed header
            }
            else {
                $('#searchBarDiv').css('position', 'inherit');
            }
        }
    },250,true));

    $(document).on("scrollstart", debounce(function () {
        //console.log("Started scrolling!");
        if (is_iOS()) {
            var yScrollPos = $(document).scrollTop();
            if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
                $('#searchBarDiv').css('position', 'fixed');
                $('#searchBarDiv').css('width', '100%');
                $('#searchBarDiv').css('top', '50px'); //50 for fixed header
            }
        }
    },250,true));

Bedarf: JQuery Mobile ist erforderlich, damit die Funktionen Startroll und Stoppscroll funktionieren.

Entprellen ist enthalten, um Verzögerungen auszugleichen, die durch das klebrige Element verursacht werden.

Getestet in iOS10.

Dima
quelle
0

Ich bin gestern über so etwas gesprungen, indem ich die Höhe von #a auf die maximal sichtbare Höhe eingestellt habe (Körpergröße war in meinem Fall), wenn #b sichtbar ist

Ex:

    <script>
    document.querySelector('#b').addEventListener('focus', function () {
      document.querySelector('#a').style.height = document.body.clientHeight;
    })
    </script>

ps: Entschuldigung für das späte Beispiel, habe gerade bemerkt, dass es gebraucht wurde.

Onur Uyar
quelle
14
Bitte
fügen
@EruPenkman sorry habe gerade deinen Kommentar bemerkt, hoffe das hilft.
Onur Uyar
0

Ich hatte das Problem, unter Codezeilen wurde es für mich behoben -

html{

 overflow: scroll; 
-webkit-overflow-scrolling: touch;

}
Manoj Gorasya
quelle