Una necessità che si incontra presto o tardi durante lo sviluppo di una web-app è quella di applicare stili diversi a specifici elementi in base all’orientamento del dispositivo, in verticale oppure orizzontale.
Essendo un intervento CSS la prima soluzione che si trova googlando l’argomento è l’uso delle famose media query con la proprietà orientation, quindi se per esempio stiamo sviluppando una barra di navigazione stile Android Holo e vogliamo che l’altezza della barra si riduca solo in modalità landscape potremmo scrivere un CSS come il seguente :
1 2 3 4 5 6 7 8 9 10 11 |
#top-bar { background : red; height : 48px; } @media all and (orientation: landscape) { #top-bar { background: purple; height: 38px; } } |
Problema risolto, giusto? Tuttavia ho notato un comportamento curioso grazie al generoso schermo del mio Galaxy Note 2 e la sua modalità Multi Window per affiancare applicazioni in esecuzione simultanea.
Come funziona la media query orientation
Il mio presupposto era che la media query interrogasse l’accelerometro del dispositivo per capire l’orientamento e quindi decidere se applicare le regole delle media query preposte oppure no, tuttavia una consultazione alle specifiche W3C nel paragrafo 4.5 delle media query rivela che il comportamento “anomalo” sarebbe quello corretto :
The ‘orientation’ media feature is ‘portrait’ when the value of the ‘height’ media feature is greater than or equal to the value of the ‘width’ media feature. Otherwise ‘orientation’ is ‘landscape’.
1 2 |
@media all and (orientation:portrait) { … } @media all and (orientation:landscape) { … } |
I valori height e width interpellati dalle media query equivalgono alle proprietà javascript window.innerHeight e window.innerWidth che rispecchiano la dimensione della finestra del browser non dello schermo del dispositivo. Ciò significa che le media query possono essere attivate anche quando l’orientamento del dispositivo non è cambiato.
Questa logica probabilmente prevede che sui dispositivi mobili la finestra del browser occupi sempre tutto lo spazio disponibile dello schermo, poiché si presuppone che le applicazioni mobile vengano eseguite una alla volta per sfruttare tutto il “poco” spazio disponibile. Tutto ciò era vero fino a quando non sono arrivati i Phablet sul mercato, e anche i tablet Android stanno iniziano ad approcciare una concezione di multi-tasking più simile a quella del desktop con un approccio a finestre.
A causa di questa logica ottengo un effetto indesiderato, poiché le applicazioni Android abilitate al Multi Window mantengono gli stili portrait/landscape della UI indipendentemente dal rapporto di altezza/larghezza della finestra di esecuzione, invece la mia web-app se ristretta in larghezza pensa di essere in modalità portrait, aumentando così la dimensione della barra, riducendo così il già limitato spazio verticale disponibile.
Questo comportamento si manifesta anche quando i campi input e textfield per l’inserimento testuale ottengono il focus e viene mostrata la tastiera virtuale. Lo spazio verticale occupato dalla tastiera viene sottratto all’altezza della finestra innescando nuovamente le media query, ma sempre senza un reale cambio di orientamento.
Armeggiando con il seguente esempio sia su desktop che mobile si capisce meglio l’entità del problema :
Gestire l’orientamento via JavaScript
Sui dispositivi dotati di interfaccia touchscreen, sia tablet che smartphone i quali generalmente hanno a disposizione anche l’accelerometro, l’oggetto window del DOM mette a disposizione la proprietà orientation, che come il nome suggerisce può essere usata per individuare l’orientamento attuale del dispositivo.
Notare che diversamente dal metodo CSS in cui nella media query esplicitiamo il nome dell’orientamento desiderato, la proprietà window.orientation è un numero intero che rappresenta i gradi di inclinazione della finestra del browser rispetto alla modalità predefinita che è sempre rappresentata dallo 0.
Eseguendo una rotazione continua in senso orario del terminale, sulla maggior parte degli smartphone ci troveremo nelle seguenti 4 situazioni.
caso |
window.orientation |
visuale |
|
verticale predefinito |
0 |
portait |
|
orizzontale senso orario |
-90 |
landscape |
|
verticale invertito |
valore precedente o 180 |
orientamento precedente o portrait |
|
orizzontale senso antiorario |
90 |
landscape |
Ci sono alcuni aspetti da sottolineare, nel caso degli smartphone il valore predefinito 0 corrisponde sempre all’orientamento verticale portrait. Su alcuni tablets, come il Galaxy Tab che per design e predisposizione dei tasti è pensato per essere impugnato in modalità landscape per l’uso quotidiano, interpellando la proprietà window.orientation probabilmente otterremo 0 per il caso landscape, questo perché il costruttore ha deciso che quella è la modalità predefinita di uso del dispositivo.
Inoltre può accadere su alcuni dispositivi che i valori orizzontali non cambino di segno nel corso di una rotazione continua, continuando a riportare 90 o -90 per entrambi i casi orizzontali, e il valore verticale invertito di 180 in molti casi non viene mai segnalato perché tale orientamento non è consentito per le app.
Dati questi aspetti possiamo affrontare la questione con il seguente JavaScript :
1 2 3 4 5 6 7 8 9 |
function setDeviceOrientation() { var isAxisDefault = window.orientation===0 || window.orientation===180, orientation = (isAxisDefault)?'portrait':'landscape'; $('html').attr('data-orientation', orientation) }; setDeviceOrientation(); $(window).on('orientationchange', setDeviceOrientation); |
Controlliamo se l’inclinazione attuale corrisponde ai valori dell’asse di default che per gli smartphone è portrait, viceversa la stessa variabile viene invece valorizzata con landscape indipendentemente dal segno riportato, infine settiamo il data attribute sul tag html.
Il funzionamento diventa quindi corretto con il seguente SASS complementare :
1 2 3 4 5 6 7 8 9 10 11 |
#top-bar { background : red; height : 48px; } [data-orientation="landscape"] { #top-bar { background : purple; height: 38px; } } |
Orientamento del dispositivo e iFrames
Eseguendo il codice di cui sopra all’interno di un iFrame il valore di window.orientation sarà sempre 0, questo perché solo il frame top viene informato del cambiamento di orientamento del dispositivo.
Il problema può essere aggirato facilmente se i due frames, padre e figlio, appartengono allo stesso dominio, nel qual caso dall’interno del frame figlio possiamo interpellare le proprietà DOM del padre attraverso l’oggetto parent, altrimenti incorreremo in un errore di sicurezza cross-origin che ci impedirà di ottenere l’informazione desiderata.
Se i due frame sono su domini diversi ma abbiamo accesso al codice del frame padre possiamo utilizzare i postMessage HTML5 per notificare al frame figlio i cambiamenti che avvengono all’interno del frame padre.