2012-07-02 12 views
14

Ho riscontrato un problema nelle versioni mobili del webkit (in particolare Webkit 534.46 su iOS 5.1.1 come Safari per dispositivi mobili e ora Chrome per iOS) che non si verifica su alcun browser desktop che I ' ho visto. (cioè le demo sottostanti devono essere visualizzati su una versione mobile WebKit.)Mobile Webkit reflow issue

Here is a live example of the issue. Il nucleo del CSS è estremamente semplice. Si posiziona un indice alfabeto lungo la sinistra della pagina:

#index { 
    left:0; margin:0; padding:0; position:fixed; top:0; width:3em; 
} 

Il problema si verifica quando un elemento è in posizione fissa sopra la parte superiore del corpo. È completamente in grado di interagire fino a quando la scroll non cambia e quindi smette di accettare input. Se I (manualmente) muove lo scroll di un solo pixel, diventa nuovamente attivo. L'esempio è stato mantenuto il più semplice possibile e non utilizza JavaScript. Dopo averlo davvero martellato, ho scoperto che sembra che l'elemento pensi che sia stato fatto scorrere ma che sia stato visivamente corretto. In altre parole, se fai clic su "A", prova nuovamente a fare clic su "A", a volte otterrai un secondo clic ma sarà più in basso nell'elenco. Mi è sembrato un problema di reflow CSS. So che il webkit mobile tenta di ridurre il numero di reflows.

Here is a live example of the workaround.

io sono in grado di utilizzare JS per forzare il CSS di tutto il documento per il reflow su di scorrimento (con una valvola a farfalla che impedisce che accada fino a 100 ms dopo lo scorrimento), che sembra per risolvere questo problema nel semplice esempio. Sfortunatamente, questo non aiuta la versione del mondo reale di questo problema.

This is the code for the issue page and the workaround script.

La mia domanda è che cosa sta accadendo qui e c'è una soluzione alternativa CSS che mi manca? In particolare, sono curioso di sapere se qualche guru dei CSS può capire quale sia la situazione del layout che impedisce ai clic di colpire la posizione corretta sull'elemento fisso? Una migliore comprensione potrebbe aiutare a trovare una soluzione reale.

Modifica: Ho dimenticato di menzionare che l'esempio costringe esplicitamente la finestra alla dimensione della finestra. Quindi l'utente non può ingrandire/ridurre, il che significa che la posizione: fissa deve ancorare l'elemento al lato sinistro della finestra.

Aggiornamento (2012-09-20): Questo problema sembra essere risolto in Mobile Safari su iOS 6 (nonché su UIWebView). Qualsiasi soluzione deve prima controllare per assicurarsi che sia su iOS < 6. Ad esempio, utilizzando CssUserAgent questo potrebbe sembrare:

if (parseFloat(cssua.ua.ios) < 6) { /* ... */ } 
+0

Sto ancora esaminando le varie soluzioni nelle due risposte per trovare il "migliore". Ho dato a entrambi un +1 per i link. Attribuirò il premio a qualsiasi risoluzione che effettivamente risolva il mio problema. Grazie! – mckamey

+0

Sto ancora cercando una soluzione che funzioni nel mio caso particolare, ma la finestra di bounty sta per scadere, quindi la assegnerò a @Paul Sweatte da quando ha risposto per primo e i suoi link hanno più varianti di soluzioni. Grazie a tutti! – mckamey

risposta

9

La risposta che effettivamente risolto il problema particolare era una variazione di una soluzione individuata in one of @Paul Sweatte's links:

Essenzialmente, un div pianura che è più alto è aggiunto il corpo. Quando viene rimosso, fa sì che il corpo possa scorrere o ridursi efficacemente. L'impostazione del ritardo su 0 ms tra aggiunta/rimozione è sufficiente per consentire al DOM di ricalcolare senza causare sfarfallio. Questo è stato lo script minimo che ho trovato che ha risolto completamente il problema per tutti gli elementi position:fixed su la mia particolare istanza di questo problema.

var hack = document.createElement("div"); 
hack.style.height = "101%"; 
document.body.appendChild(hack); 
setTimeout(function(){ 
    document.body.removeChild(hack); 
    hack = null; 
}, 0); 
2

Sembra che questo è un known bug:

il problema centrale è: se la pagina si sposta in modo programmatico (cioè l'utente non ha causato lo scroll) gli elementi all'interno dell'elemento fix non sono disponibili.

Utilizzare il posizionamento assoluto, change the markup oppure utilizzare uno dei modelli ibridi workarounds.

+0

Si noti che l'esempio che ho postato in particolare non utilizza JavaScript per scorrere. Quindi questo è puramente un problema CSS. – mckamey

+0

Grazie. Ho aggiornato la mia risposta con nuove informazioni. –

2

mia installazione di iWebInspector è abbastanza sballato in questo momento, ma dopo nei guai con jsfiddle e iOS sim sembra che la vostra intuizione è corretta - pur essendo position: fixed, il browser pensa che la pagina è scorrimento, e le viti su per le clicca obiettivi.

Sembra che questo sia lo stesso problema di iOS Safari: Anchors within a fixed positioned element only work once, che non è stato risolto con CSS puro. Relativo anche: Fixed position navbar only clickable once in Mobile Safari on iOS5.

Tangenzialmente, e sono sicuro che è già stato notato, non è possibile scorrere il lato sinistro, quindi su un iPhone l'indice mostra solo A-M.

+1

Grazie. Sì, non mi sono preoccupato di rendere il layout reattivo sul lato sinistro, ho provato a rimuovere tutto ciò che non era correlato al bug. – mckamey

+0

Sì, ha senso. Sono contento che tu abbia trovato una soluzione praticabile. – egid

6

Ironia della sorte, la mia correzione di reflow originale (collegata alla domanda) ora funziona anche nella mia vera app. Mettere una variante di questo qui nel caso sia utile a chiunque altro. Può essere richiamato su qualsiasi elemento del contenitore o, se non viene passato nulla, esso riflette l'intero documento.

var forceReflow = function(elem){ 
    elem = elem || document.documentElement; 

    // force a reflow by increasing size 1px 
    var width = elem.style.width, 
     px = elem.offsetWidth+1; 

    elem.style.width = px+'px'; 

    setTimeout(function(){ 
     // undo resize, unfortunately forces another reflow 
     elem.style.width = width; 
     elem = null; 
    }, 0); 
}; 

La cosa bella di questo è che non richiede la creazione/aggiunta/rimozione di elementi, semplicemente modificando il contenitore.

+0

Forse sapete come evitare di mostrare il contenuto rotto prima del reflow? È visibile per un tempo molto breve ~ 50ms, ma è notevole per gli utenti. –

+0

Vedete il contenuto danneggiato anche quando 'setTimeout' usa un valore di' 0'? In tal caso, potrebbe essere necessario eseguire ottimizzazioni generali che migliorino la velocità di rendering della pagina. [Prova alcuni dei suggerimenti qui.] (Https://developers.google.com/speed/pagespeed/) In genere quello che ho visto è gente che usa l'evento ready di jQuery per costruire la loro interfaccia utente che è molto lenta. – mckamey

+0

IIRC, aggiungendo una classe fittizia a un elemento (ad es., '' Foo '+ Date.now() ') dovrebbe causare un riflusso e non è necessario rimuovere la classe (a meno che non ci si preoccupi della pulizia). – hayavuk

0

Credo che sia meglio, e ottiene lo stesso effetto, permettendo ai link di essere cliccabili in piedi fissi. In qualche modo, nascondendo urlbar, i link nel footer fisso risultano non modificabili finché non si scorre un po '. Ho visto anche questo quando ho messo a fuoco gli input, e allego anche un gestore di eventi a tutti gli eventi di messa a fuoco per attivarli. Lo faccio con il dojo per allegare gli eventi.

 if(navigator.userAgent.match(/iPhone/i)){ 
     /* The famous iOS can't-click-links until touch fix, I attach onfocus */ 
      query('input,textarea,select', this.domNode).on('focus', function(el){ 
       document.documentElement.style.paddingRight = '1px'; 
       setTimeout(function() { 
       document.documentElement.style.paddingRight = ''; 
       }, 0); 
      }); 
     } 
+1

Meglio di cosa? È la stessa soluzione di [il mio originale] (http: // StackOverflow.it/a/11479118/43217) ma con valori hardcoded. Ciò che deve accadere è forzare il reflow. – mckamey

0

Ecco una variante della soluzione di McKamey. Evita di riflettere due volte e può essere d'aiuto con lo sfarfallio (a seconda dell'app):

setTimeout(function(){ 
    document.body.style.borderBottom = 
     document.body.style.borderBottom === 'none' ? '1px solid white' : 'none'; 
}, 0); 
+0

Non è ancora necessario rimuovere la linea bianca (specialmente su uno sfondo non bianco)? Quello sarebbe il tuo secondo riflusso. – mckamey

+0

È possibile impostare il colore su trasparente su uno sfondo non bianco per evitare un secondo riflusso. – csytan