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>

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.


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.


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

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

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.


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.


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.


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


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.


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.


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.


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;

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.