webentwicklung-frage-antwort-db.com.de

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

In IOS8 Safari gibt es einen neuen Fehler, dessen Position behoben wurde.

Wenn Sie einen Textbereich in einem festen Bereich fokussieren, führt Safari einen Bildlauf zum Ende der Seite durch.

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>
89
Sam Saffron

Dies ist jetzt in iOS 10.3 behoben!

Hacks sollten nicht mehr benötigt werden.

2
Sam Saffron

Basierend auf diesem gute Analyse dieser Ausgabe habe ich dies in html und body Elementen 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.

54

Die beste Lösung, die ich finden konnte, ist, auf position: absolute; Im Fokus umzuschalten und die Position zu berechnen, an der es sich befand, als es position: fixed; Verwendete. Der Trick ist, dass das Ereignis focus zu spät ausgelöst wird, daher muss touchstart verwendet werden.

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

Bedarf:

Das body -Element 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 für Ihren speziellen 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 derselbe Code ohne den Hack zum Vergleich .

Vorbehalt:

Wenn das Element position: fixed; Neben body noch andere übergeordnete Elemente hat, kann ein Wechsel zu position: absolute; Zu unerwartetem Verhalten führen. Aufgrund der Art von position: fixed; Ist dies wahrscheinlich kein großes Problem, da das Verschachteln solcher Elemente nicht häufig vorkommt.

Empfehlungen:

Während die Verwendung des Ereignisses touchstart die meisten Desktop-Umgebungen herausfiltert, möchten Sie wahrscheinlich User-Agent-Sniffing verwenden, damit dieser Code nur für das kaputte iOS 8 und nicht für andere Geräte wie Android und ältere iOS-Versionen. 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.

34

Ich habe eine Methode gefunden, die funktioniert , ohne dass in die absolute Position gewechselt 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);
    }
});

Zerlegen

Zuerst muss das feste Eingabefeld im HTML-Code oben auf der Seite sein (es ist ein festes Element, daher sollte es semantisch sinnvoll sein, es sowieso 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 brauchen Sie eine Möglichkeit, iOS-Geräte zu erkennen, damit Dinge, die nicht repariert werden müssen, davon nicht betroffen sind (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, da wir alles haben, was wir brauchen, ist hier die Lösung :)

//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);
    }
});
8
Daniel Tonon

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

Dies ist nicht unbedingt eine gute Lösung, aber es ist 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)});
4
user3411121

Ganz wie Mark Ryan Sallee vorgeschlagen hat, stellte ich fest, dass das dynamische Ändern der Höhe und des Überlaufs meines Hintergrundelement der Schlüssel ist - Safari hat dadurch nichts, zu dem Sie scrollen können.

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

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

Wenn Sie das Modal schließen, ändern Sie es wieder:

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

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

2
Matthew Levy

Sauber? Nein.

Ich hatte vor kurzem dieses Problem 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 Auswahl die Position des festen Elements absolut zu machen, anstatt sie mit einem oberen Rand zu fixieren Position basierend auf der Bildlaufposition des Dokuments.

Dies ist jedoch sehr hässlich und führt immer noch zu seltsamen Scrollen vor und zurück, bevor ich an der richtigen Stelle lande, aber es ist das Nächste, was ich bekommen könnte.

Jede andere Lösung würde das Überschreiben der standardmäßigen Bildlaufmechanik des Browsers beinhalten.

1
Samuel

Ich hatte das Problem, unten Codezeilen löste es für mich -

html{

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

}
0
Manoj Gorasya

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, je nach Ihrem Design). Dies kann dazu führen, dass der Browser nicht irgendwo "nach unten" verschoben wird, zu dem gescrollt werden kann.

0

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

Background: Ich verwende eine feste Kopfzeile und ein Element weiter unten, das darunter haftet, sobald der Benutzer so weit nach unten scrollt. Dieses Element hat ein Suchfeld. Außerdem habe ich dynamische Seiten hinzugefügt, während ich vorwärts und rückwärts scrolle.

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

Anforderungen: JQuery Mobile ist erforderlich, damit die Startsroll- und Stoppscroll-Funktionen funktionieren.

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

Getestet in iOS10.

0
Dima

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

  • Überwachen Sie Klickereignisse auf einem Div
  • fokussiere ein verstecktes Eingabefeld, um die Tastatur zu rendern
  • replizieren Sie den Inhalt des ausgeblendeten Eingabefelds in das falsche 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

0
davidcondrey

Ich bin gestern über so etwas gesprungen, indem ich die Höhe von #a auf die maximale 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.

0
Onur Uyar