Safari in ios8 scorre lo schermo quando gli elementi fissi vengono attivati


96

In IOS8 Safari c'è un nuovo bug con la posizione corretta.

Se metti a fuoco un'area di testo che si trova in un pannello fisso, Safari ti farà scorrere fino alla fine della pagina.

Ciò rende impossibile lavorare con tutti i tipi di interfaccia utente, dal momento che non hai modo di inserire testo in aree di testo senza scorrere la pagina fino in fondo e perdere il tuo posto.

C'è un modo per risolvere questo bug in modo pulito?

#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
L'impostazione di uno z-index su #b sarebbe d'aiuto?
Ben

1
L'indice z non è di aiuto, forse una trasformazione di css senza operazioni sarebbe molto utile con i contesti dello stack, non sono sicuro.
Sam Saffron

1
per il contesto ecco la discussione su Discourse: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/…
Sam Saffron

79
iOS Safari è il nuovo IE
geedubb

4
@geedubb concordato. qualsiasi sistema operativo idiota che lega la sua versione del browser predefinita al sistema operativo cadrà in fallo sui problemi che hanno afflitto IE negli ultimi 7 anni.
rugiada il

Risposte:


58

Sulla base di questa buona analisi di questo problema, ho usato questo in htmled bodyelementi in CSS:

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

Penso che funzioni benissimo per me.


2
ha funzionato anche per me. Questo ha incasinato molte altre cose da quando sto manipolando il mio DOM al caricamento, quindi lo trasformo in una classe e l'ho aggiunto al corpo html, dopo che il DOM si è stabilizzato. Cose come scrollTop non funzionano molto bene (sto facendo lo scorrimento automatico), ma ancora una volta, puoi aggiungere / rimuovere la classe mentre esegui le operazioni di scorrimento. Scarso lavoro da parte del team di Safari però.
Amarsh

1
Persone in cerca di questa opzione potrebbe anche voler prova transform: translateZ(0);da stackoverflow.com/questions/7808110/...
lkraav

1
Questo risolve il problema, ma se hai animazioni appariranno molto instabili. Potrebbe essere meglio racchiuderlo in una media query.
mmla

Ha funzionato per me su iOS 10.3.
quotesBro

Non risolve il problema. È necessario intercettare lo scorrimento quando spettacoli tastiera virtuale e l'altezza modifica al valore specifico: stackoverflow.com/a/46044341/84661
Brian Cannard

36

La soluzione migliore che sono riuscito a trovare è passare all'uso position: absolute;della messa a fuoco e calcolare la posizione in cui si trovava quando era in uso position: fixed;. Il trucco è che l' focusevento si attiva troppo tardi, quindi touchstartdeve essere utilizzato.

La soluzione in questa risposta imita molto da vicino il comportamento corretto che abbiamo avuto in iOS 7.

Requisiti:

L' bodyelemento deve avere un posizionamento per garantire il corretto posizionamento quando l'elemento passa al posizionamento assoluto.

body {
    position: relative;
}

Il codice ( esempio dal vivo ):

Il codice seguente è un esempio di base per il test case fornito e può essere adattato per il tuo caso d'uso specifico.

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

Ecco lo stesso codice senza l'hack per il confronto .

Avvertimento:

Se l' position: fixed;elemento ha altri elementi padre con posizionamento oltre body, il passaggio a position: absolute;potrebbe avere un comportamento imprevisto. A causa della natura di position: fixed;ciò, probabilmente non è un grosso problema, poiché la nidificazione di tali elementi non è comune.

Raccomandazioni:

Sebbene l'uso touchstartdell'evento filtrerà la maggior parte degli ambienti desktop, probabilmente vorrai utilizzare lo sniffing dell'agente utente in modo che questo codice venga eseguito solo per iOS 8 rotto e non altri dispositivi come Android e versioni iOS precedenti. Sfortunatamente, non sappiamo ancora quando Apple risolverà questo problema in iOS, ma sarei sorpreso se non fosse risolto nella prossima versione principale.


Mi chiedo se il doppio avvolgimento con un div e l'impostazione dell'altezza a% 100 su un div di avvolgimento trasparente possa indurlo a evitare questo ...
Sam Saffron

@SamSaffron Potresti chiarire come potrebbe funzionare una tecnica del genere? Ho provato alcune cose come questa senza successo. Poiché l'altezza del documento è ambigua, non sono sicuro di come potrebbe funzionare.
Alexander O'Mara

Stavo pensando che semplicemente un involucro "fisso" al 100% di altezza potrebbe funzionare intorno a questo, forse no
Sam Saffron

@downvoter: ho sbagliato qualcosa? Sono d'accordo che questa sia una soluzione terribile, ma non credo che ce ne siano di migliori.
Alexander O'Mara

4
Questo non ha funzionato per me, il campo di input si muove ancora.
Rodrigo Ruiz

8

Ho trovato un metodo che funziona senza la necessità di passare alla posizione assoluta!

Codice completo non commentato

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

Abbattendolo

Per prima cosa devi avere il campo di input fisso verso la parte superiore della pagina nell'HTML (è un elemento fisso quindi dovrebbe avere senso semanticamente averlo comunque vicino alla parte superiore):

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

Quindi è necessario salvare la posizione di scorrimento corrente nelle variabili globali:

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

Quindi hai bisogno di un modo per rilevare i dispositivi iOS in modo che non influisca su cose che non richiedono la correzione (funzione presa da 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;
}

Ora che abbiamo tutto ciò di cui abbiamo bisogno, ecco la soluzione :)

//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. Questa è una soluzione significativamente meno complicata rispetto alla risposta accettata, se hai una gerarchia DOM non banale. Questo dovrebbe avere più voti positivi
Anson Kao

Potresti fornire anche questo in JS nativo? Grazie mille!
mesqueeb

@ SamSaffron, questa risposta ha funzionato davvero per te? posso avere qualche esempio qui. non ha funzionato per me?
Ganesh Putta

@ SamSaffron, questa risposta ha davvero risolto il tuo problema, puoi inviare qualche esempio che ha funzionato per te, sto lavorando allo stesso, ma non ha funzionato per me.
Ganesh Putta

@GaneshPutta È possibile che un aggiornamento iOS più recente abbia fatto sì che non funzionasse più. L'ho postato 2,5 anni fa. Dovrebbe comunque funzionare se hai seguito esattamente tutte le istruzioni: /
Daniel Tonon

4

Sono stato in grado di risolvere questo problema per gli input selezionati aggiungendo un listener di eventi agli elementi di selezione necessari, quindi scorrendo di un offset di un pixel quando la selezione in questione ottiene il focus.

Questa non è necessariamente una buona soluzione, ma è molto più semplice e affidabile delle altre risposte che ho visto qui. Il browser sembra rieseguire il rendering / ricalcolare la posizione: fisso; attributo basato sull'offset fornito nella funzione window.scrollBy ().

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

2

Proprio come suggerito da Mark Ryan Sallee, ho scoperto che cambiare dinamicamente l'altezza e l'overflow del mio elemento di sfondo è la chiave: questo non dà a Safari nulla su cui scorrere.

Quindi, al termine dell'animazione di apertura del modale, modifica lo stile dello sfondo:

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

Quando chiudi il modale, cambialo di nuovo:

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

Mentre altre risposte sono utili in contesti più semplici, il mio DOM era troppo complicato (grazie SharePoint) per utilizzare lo scambio di posizione assoluta / fissa.


1

Pulito? no.

Recentemente ho avuto questo problema con un campo di ricerca fisso in un'intestazione appiccicosa, il meglio che puoi fare al momento è mantenere la posizione di scorrimento in una variabile in ogni momento e al momento della selezione rendere assoluta la posizione dell'elemento fisso invece di fissata con una parte superiore posizione in base alla posizione di scorrimento del documento.

Questo è comunque molto brutto e si traduce comunque in uno strano scorrimento avanti e indietro prima di atterrare nel posto giusto, ma è il più vicino possibile.

Qualsiasi altra soluzione comporterebbe l'override della meccanica di scorrimento predefinita del browser.


1

Ora è stato risolto in iOS 10.3!

Gli hack non dovrebbero più essere necessari.


1
Puoi indicare eventuali note di rilascio che indicano che è stato risolto?
bluepnume

Apple è molto riservata, hanno chiuso la mia segnalazione di bug Ho confermato che ora funziona correttamente, questo è tutto ciò che ho :)
Sam Saffron

Ho ancora questo problema su iOS 11
zekia

0

Non ho affrontato questo particolare bug, ma forse ho messo un overflow: hidden; sul corpo quando l'area di testo è visibile (o semplicemente attiva, a seconda del design). Ciò potrebbe avere l'effetto di non dare al browser un punto "in basso" per scorrere.


1
Non riesco nemmeno a far sì che il touchstart si attivi abbastanza presto da considerare anche quell'hack :(
Sam Saffron

0

Una possibile soluzione sarebbe sostituire il campo di input.

  • Monitora gli eventi di clic su un div
  • focalizza un campo di input nascosto per eseguire il rendering della tastiera
  • replicare il contenuto del campo di input nascosto nel campo di input falso

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

Nessuna di queste soluzioni ha funzionato per me perché il mio DOM è complicato e ho pagine dinamiche di scorrimento infinito, quindi ho dovuto crearne una mia.

Sfondo: sto usando un'intestazione fissa e un elemento più in basso che rimane sotto di essa una volta che l'utente scorre così in basso. Questo elemento ha un campo di input di ricerca. Inoltre, ho aggiunto pagine dinamiche durante lo scorrimento in avanti e all'indietro.

Problema: in iOS, ogni volta che l'utente fa clic sull'input nell'elemento fisso, il browser scorre fino alla parte superiore della pagina. Ciò non solo ha causato un comportamento indesiderato, ma ha anche attivato l'aggiunta della mia pagina dinamica nella parte superiore della pagina.

Soluzione prevista: nessuno scorrimento in iOS (nessuno) quando l'utente fa clic sull'input nell'elemento adesivo.

Soluzione:

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

Requisiti: JQuery mobile è richiesto per il funzionamento delle funzioni startsroll e stopscroll.

Il rimbalzo è incluso per appianare qualsiasi ritardo creato dall'elemento adesivo.

Testato in iOS10.


0

Ho appena saltato qualcosa di simile ieri impostando l'altezza di #a all'altezza massima visibile (l'altezza del corpo era nel mio caso) quando #b è visibile

ex:

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

ps: scusa per il tardo esempio, ho appena notato che era necessario.


14

@EruPenkman scusa ho appena notato il tuo commento, spero che aiuti.
Onur Uyar

0

Ho avuto il problema, sotto le righe di codice l'ho risolto per me -

html{

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.