Safari w ios8 przewija ekran, gdy skupiają się na stałych elementach

96

W Safari IOS8 pojawił się nowy błąd z poprawioną pozycją.

Jeśli skupisz się na obszarze tekstowym, który znajduje się w stałym panelu, safari przewinie cię na dół strony.

Uniemożliwia to pracę z wszelkiego rodzaju interfejsami użytkownika, ponieważ nie ma możliwości wprowadzania tekstu do obszarów tekstowych bez przewijania strony do końca w dół i utraty miejsca.

Czy istnieje sposób na łatwe obejście tego błędu?

#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 Saffron
źródło
1
Czy ustawienie indeksu Z na #b pomoże?
Ben
1
Indeks z nie jest pomocny, być może jakaś wymyślna żadna transformacja op css nie byłaby zbyt dobra w kontekstach stosu, nie jestem pewien.
Sam Saffron
1
kontekst tutaj jest dyskusja na temat Discourse: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/ ...
Sam Saffron
80
iOS Safari to nowy IE
geedubb
4
@geedubb się zgodził. każdy kretyński system operacyjny, który łączy swoją domyślną wersję przeglądarki z systemem operacyjnym, będzie miał problemy z problemami, które nękały IE przez ostatnie 7 lat.
dewd

Odpowiedzi:

58

Opierając się na tej dobrej analizie tego problemu, użyłem tego w elementach htmli bodyw css:

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

Myślę, że to działa świetnie dla mnie.

Mohammad AlBanna
źródło
2
dla mnie też pracował. Spieprzyło to wiele innych rzeczy, ponieważ manipuluję moim DOM podczas ładowania, więc robię to w klasę i dodałem ją do treści html, po ustabilizowaniu DOM. Rzeczy takie jak scrollTop nie działają zbyt dobrze (robię automatyczne przewijanie), ale znowu możesz dodać / usunąć klasę podczas wykonywania operacji przewijania. Jednak słaba praca części zespołu Safari.
Amarsh
1
Osoby oglądające tę opcję mogą również chcieć przetestować transform: translateZ(0);ze stackoverflow.com/questions/7808110/ ...
lkraav
1
To rozwiązuje problem, ale jeśli masz animacje, będą wyglądać na bardzo nierówne. Lepiej byłoby zawrzeć to w zapytaniu o media.
mmla,
Pracował dla mnie na iOS 10.3.
cytuje Brat
To nie rozwiązuje problemu. Musisz przechwycić przewijanie, gdy pojawia się wirtualna klawiatura i zmienić wysokość na określoną wartość: stackoverflow.com/a/46044341/84661
Brian Cannard
36

Najlepszym rozwiązaniem, jakie mogłem wymyślić, jest przejście na używanie position: absolute;ostrości i obliczanie pozycji, w której się znajdował, gdy był używany position: fixed;. Rzecz w tym, że focuszdarzenie jest uruchamiane za późno, więc touchstartnależy je wykorzystać.

Rozwiązanie w tej odpowiedzi bardzo dokładnie naśladuje prawidłowe zachowanie, które mieliśmy w iOS 7.

Wymagania:

bodyElement musi mieć pozycjonowania w celu zapewnienia właściwego położenia, gdy element przełącza bezwzględnego położenia.

body {
    position: relative;
}

Kod ( przykład na żywo ):

Poniższy kod jest podstawowym przykładem dostarczonego przypadku testowego i może być dostosowany do konkretnego przypadku użycia.

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

Oto ten sam kod bez hackowania dla porównania .

Ostrzeżenie:

Jeśli position: fixed;element ma inne elementy nadrzędne z pozycjonowaniem poza tym body, przełączenie na position: absolute;może mieć nieoczekiwane zachowanie. Ze względu na swój charakter position: fixed;nie jest to prawdopodobnie poważny problem, ponieważ zagnieżdżanie takich elementów nie jest powszechne.

Zalecenia:

Podczas gdy użycie touchstartzdarzenia spowoduje odfiltrowanie większości środowisk graficznych, prawdopodobnie będziesz chciał użyć wykrywania klienta użytkownika, aby ten kod działał tylko dla zepsutego iOS 8, a nie innych urządzeń, takich jak Android i starsze wersje iOS. Niestety nie wiemy jeszcze, kiedy Apple naprawi ten problem w iOS, ale zdziwiłbym się, gdyby nie został naprawiony w kolejnej większej wersji.

Alexander O'Mara
źródło
Zastanawiam się, czy owijanie podwójne z div i wysokości ustawienia do 100% na przezroczystym owijania div mogłoby skłonić go do unikania tego ...
Sam Saffron
@SamSaffron Czy mógłbyś wyjaśnić, jak taka technika może działać? Próbowałem kilku takich rzeczy bez powodzenia. Ponieważ wysokość dokumentu jest niejednoznaczna, nie jestem pewien, jak mógłby on działać.
Alexander O'Mara,
Myślałem, że po prostu „ustalone” opakowanie na 100% wysokości może obejść ten problem, prawdopodobnie nie
Sam Saffron
@downvoter: Czy coś się nie udało? Zgadzam się, że to okropne rozwiązanie, ale nie sądzę, aby były lepsze.
Alexander O'Mara
4
To nie zadziałało, pole wejściowe nadal się porusza.
Rodrigo Ruiz
8

Znalazłem metodę, która działa bez konieczności zmiany pozycji na absolutną!

Pełny kod bez komentarzy

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

Zniszczyć to

Najpierw musisz mieć stałe pole wejściowe u góry strony w HTML (jest to stały element, więc semantycznie i tak powinno mieć sens, aby było ono u góry strony):

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

Następnie musisz zapisać aktualną pozycję przewijania do zmiennych globalnych:

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

Następnie potrzebujesz sposobu na wykrycie urządzeń iOS, aby nie wpływał na rzeczy, które nie wymagają naprawy (funkcja pobrana z 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;
}

Teraz, gdy mamy wszystko, czego potrzebujemy, oto poprawka :)

//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
źródło
+1. Jest to znacznie mniej skomplikowana poprawka niż akceptowana odpowiedź, jeśli masz nietrywialną hierarchię DOM. To powinno mieć więcej pozytywnych głosów
Anson Kao
Czy możesz podać to również w natywnym JS? Dzięki wielkie!
mesqueeb
@SamSaffron, czy ta odpowiedź naprawdę Ci pomogła? czy mogę tu mieć jakiś przykład. to nie zadziałało dla mnie?
Ganesh Putta,
@ SamSaffron, czy ta odpowiedź naprawdę rozwiązała twój problem, czy możesz wysłać jakiś przykład, który działał dla ciebie, pracuję nad tym samym, ale to nie zadziałało dla mnie.
Ganesh Putta,
@GaneshPutta Możliwe, że nowsza aktualizacja iOS sprawiła, że ​​to już nie działa. Opublikowałem to 2,5 roku temu. Powinno jednak nadal działać, jeśli dokładnie zastosujesz się do wszystkich instrukcji: /
Daniel Tonon,
4

Udało mi się to naprawić dla wybranych danych wejściowych, dodając odbiornik zdarzeń do niezbędnych elementów wyboru, a następnie przewijając o przesunięcie o jeden piksel, gdy zaznaczony element staje się aktywny.

Niekoniecznie jest to dobre rozwiązanie, ale jest znacznie prostsze i bardziej niezawodne niż inne odpowiedzi, które tu widziałem. Wygląda na to, że przeglądarka ponownie renderuje / ponownie oblicza pozycję: naprawiona; atrybut oparty na przesunięciu podanym w funkcji window.scrollBy ().

document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
user3411121
źródło
2

Podobnie jak zasugerował Mark Ryan Sallee, odkryłem, że kluczem jest dynamiczna zmiana wysokości i przepełnienia mojego elementu tła - to nie daje Safari nic do przewijania.

Więc po zakończeniu animacji otwierającej modal zmień stylizację tła:

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

Po zamknięciu modalu zmień go z powrotem:

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

Podczas gdy inne odpowiedzi są przydatne w prostszych kontekstach, mój DOM był zbyt skomplikowany (dzięki SharePoint), aby używać zamiany pozycji bezwzględnej / stałej.

Matthew Levy
źródło
1

Czysty? Nie.

Niedawno miałem ten problem ze stałym polem wyszukiwania w lepkim nagłówku, najlepsze, co możesz teraz zrobić, to utrzymywać pozycję przewijania w zmiennej przez cały czas, a po zaznaczeniu ustaw pozycję stałego elementu jako absolutną zamiast ustaloną górą położenie na podstawie pozycji przewijania dokumentu.

Jest to jednak bardzo brzydkie i nadal powoduje dziwne przewijanie w przód iw tył przed wylądowaniem we właściwym miejscu, ale jest to najbliższe, jakie mogłem uzyskać.

Każde inne rozwiązanie wymagałoby zastąpienia domyślnej mechaniki przewijania przeglądarki.

Samuel
źródło
0

Nie poradziłem sobie z tym konkretnym błędem, ale może wstawiłem przepełnienie: ukryty; na treści, gdy obszar tekstu jest widoczny (lub po prostu aktywny, w zależności od projektu). Może to skutkować brakiem możliwości przewinięcia przeglądarki w dowolnym miejscu.

Mark Ryan Sallee
źródło
1
Nie wydaje mi się, żebym uruchomił się na tyle wcześnie, aby nawet rozważyć ten hack :(
Sam Saffron
0

Możliwym rozwiązaniem byłoby zastąpienie pola wejściowego.

  • Monitoruj zdarzenia kliknięcia w div
  • skupić się na ukrytym polu wejściowym, aby renderować klawiaturę
  • powielić zawartość ukrytego pola wejściowego do fałszywego pola wejściowego

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

Żadne z tych rozwiązań nie zadziałało dla mnie, ponieważ mój DOM jest skomplikowany i mam dynamiczne nieskończone przewijane strony, więc musiałem stworzyć własny.

Tło: używam stałego nagłówka i elementu znajdującego się niżej, który pozostaje pod nim, gdy użytkownik przewinie tak daleko w dół. Ten element ma pole wyszukiwania. Dodatkowo mam dodane dynamiczne strony podczas przewijania do przodu i do tyłu.

Problem: W systemie iOS za każdym razem, gdy użytkownik kliknął dane wejściowe w stałym elemencie, przeglądarka przewijała się na samą górę strony. To nie tylko spowodowało niepożądane zachowanie, ale także spowodowało dodanie mojej dynamicznej strony u góry strony.

Oczekiwane rozwiązanie: brak przewijania w systemie iOS (w ogóle brak), gdy użytkownik kliknie dane wejściowe w przyklejonym elemencie.

Rozwiązanie:

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

Wymagania: JQuery mobile jest wymagany do działania funkcji startroll i stopcroll.

Odbicie jest włączone, aby wygładzić wszelkie opóźnienia utworzone przez element sticky.

Testowane w iOS10.

Dima
źródło
0

Właśnie przeskoczyłem coś takiego wczoraj, ustawiając wysokość #a na maksymalną widoczną wysokość (w moim przypadku była to wysokość ciała), gdy #b jest widoczne

dawny:

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

ps: przepraszam za późny przykład, właśnie zauważyłem, że jest potrzebny.

Onur Uyar
źródło
14
Dołącz przykładowy kod, aby wyjaśnić, w jaki sposób Twoja poprawka może pomóc
roo2
@EruPenkman przepraszam, właśnie zauważyłem twój komentarz, mam nadzieję, że to pomoże.
Onur Uyar
0

Zostało to naprawione w iOS 10.3!

Hacki nie powinny już być potrzebne.

Sam Saffron
źródło
1
Czy możesz wskazać jakieś uwagi do wydania, które wskazują na to, że zostało to naprawione?
bluepnume
Apple są bardzo tajemnicze, zamknęli mój raport o błędzie Potwierdziłem, że teraz działa poprawnie, to wszystko, co mam :)
Sam Saffron
1
Nadal mam ten problem na iOS 11
zekia,
Nie, to wciąż problem nawet na iOS 13.
Dmitriy Khudorozhkov
0

Miałem problem, poniższe linie kodu rozwiązały go za mnie -

html{

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

}
Manoj Gorasya
źródło