Sono serviti diversi anni per far abbandonare agli sviluppatori web le tabelle come strumento di creazione dei layout in favore di elementi semanticamente più rilevanti combinati con il CSS, ma il brutto effetto collaterale che ha avuto la campagna di sensibilizzazione verso la costruzione di layout flessibili con i fogli di stile è stata la completa demonizzazione delle tabelle HTML e delle stesse proprietà CSS che permettono di avvalersi dei loro potenti algoritmi di calcolo delle dimensioni.
Mi capita sempre più spesso di imbattermi in strutture tabellari formattate a mezzo di div, p, span e altri tag testuali con l’uso di strutture CSS a larghezze fisse, che sarebbero state ottenute in maniera molto più semplice (ed efficace in ottica responsive) usando il buon vecchio tag table perfettamente lecito in tale contesto.
Questa paura radicata dell’uso delle tabelle ormai viste come uno strumento desueto del passato può farci perdere di vista le grandi potenzialità che le CSS table hanno anche in relazione alle nuovissime frontiere del web quali le mobile webapp, sia native con tecnologia PhoneGap che non.
I tre elementi verticali di un layout mobile
Generalmente il layout di un’applicazione mobile si compone delle seguenti parti principali disposte verticalmente in quest’ordine :
- Header
- Contenuti
- Comandi
L’Header contiene il logo ed il nome dell’app o della vista corrente ed uno o più tasti interattivi. Questa zona inoltre può contenere ulteriori righe di elementi che spingono ulteriormente verso il basso il box dei Contenuti.
La parte dei Comandi può non essere sempre presente in iOS e Windows Phone, mentre in Android è possibile trovarli in questa zona ma generalmente vengono posizionati nella riga sotto l’Header.
La parte centrale dei contenuti ospita di volta in volta gli elementi pertinenti alla vista selezionata ed è scrollabile verticalmente indipendentemente dagli elementi circostanti, tuttavia l’altezza del box dei Cotenuti si deve adattare per rientrare tra i due elementi senza mandare fuori schermo nessuno dei tre.
Questo comportamento si ottiene facilmente e in maniera crossbrowser con il seguente HTML :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<div id="Layout"> <header class="layout-top"> HEADER. </header> <section class="layout-middle"> <div class="outWrap"> <div class="inWrap"> CONTENTS. </div> </div> </section> <footer class="layout-bottom"> FOOTER. </footer> </div> |
ed il seguente SASS/CSS :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
html, body, #Layout { font-family: arial; height: 100%; margin: 0; overflow: hidden; width: 100%; } #Layout { display: table; .layout-top { background-color: orange; display: table-header-group; height: 1%; } .layout-middle { background-color: azure; display: inherit; height: 100%; min-height: 1px; width: 100%; position: relative; .outWrap { display: block; height: 100%; position : absolute; overflow: auto; -webkit-overflow-scrolling: touch; width:100%; .inWrap { height: 100%; } } } .layout-bottom { background-color: grey; display: table-footer-group; height: 1%; } } // Targets Android legacy stockbrowser @media screen and (-webkit-min-device-pixel-ratio:0) { #Layout { .layout-middle { .outWrap { position: static; } } } } // Cancel the above and restore position on Safari 10+ _::-webkit-:host:not(:root:root), #Layout .layout-middle .outWrap { position: absolute; } |
I due elementi .layout-top e .layout-bottom, aventi rispettivamente le proprietà display:<strong>table-header-group</strong> e display:<strong>table-footer-group</strong> fanno in modo che la propria altezza sia determinata dai loro contenuti, se assenti gli elementi collassano lasciando lo spazio all’elemento centrale .layout-middle avente display:<strong>table</strong> come il padre principale e la direttiva height:100%.
Inoltre la proprietà position è impostata su relative, questo perchè i contenuti saranno ospitati da due div interni, il primo dei quali .outWrap in position:absolute per fare in modo che occupi verticalmente sempre l’altezza del suo genitore, ovvero la riga dinamica .layout-middle in display:table, mentre il div interno .inWrap si espanderà verticalmente in maniera normale, solo che quando i contenuti eccederanno in altezza lo spazio di .layout-middle si avrà lo scrolling all’interno di .outWrap in modo da non intaccare il resto della struttura.
Internet Explorer Legacy
Volendo applicare questa questa struttura HTML/CSS in ambito desktop la punta dolente è naturalmente Internet Explorer, nello specifico la struttura funziona egregiamente anche in IE8, che è la versione più aggiornata disponibile sui vetusti sistemi che operano con Window XP (dismesso per la gioia di tutti l’8 aprile 2014), con la seguente modifica CSS :
1 2 3 4 5 6 7 8 9 |
.ie8 { #Layout { .layout-middle { display: table-cell; height : auto; } } } |
La versione più problematica è invece IE9 che, nonostante la seguente fix CSS, presenta un comportamento anomalo :
1 2 3 4 5 6 7 8 |
.ie9 { #Layout { .layout-middle { display: table-cell; } } } |
Nello specifico vedremo che l’area azzurra dei Contenuti di .layout-middle si espande correttamente in altezza ma nessun contenuto appare all’interno. Il problema è causato dalla computazione dell’altezza dell’elemento a livello di box-model, nonostante l’altezza dichiarata 100% sull’elemento per espandersi entro tutto lo spazio verticale disponibile tra i due fratelli, che viene rispettata a livello visivo, il valore computato per l’altezza mostrata dall’inspector risulta 0.
In questo modo si spezza la catena di propagazione delle altezze dinamiche verso i suoi figli, dato che l’elemento .outWrap in position:absolute avrà come riferimento zero e di conseguenza non sarà disegnato sullo schermo.
Tuttavia richiedendo l’altezza dell’elemento via JavaScript otteniamo il valore corretto, per cui l’unico work-around per questo strano comportamento è “propagare” via JavaScript l’altezza di .layout-middle applicandola ad .outWrap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
//IE detection $(document).ready(function(){ var bv = navigator.appVersion; var ie = ''; if( bv.indexOf("MSIE 8.")!=(-1) ) { ie = 'ie8'; } else if( bv.indexOf("MSIE 9.")!=(-1) ) { ie = 'ie9'; } else if( bv.indexOf("MSIE 10.")!=(-1) ) { ie = 'ie10'; } $('html').addClass(ie); //Fix IE9 layout propagation bug if(ie==='ie9') { function setHeight() { var parentHeight = $('.layout-middle').height(); $('.outWrap').height(parentHeight); } setHeight(); $(window).on('resize', function(){ setHeight(); }); } }); |
In questo modo la struttura nel complesso continua ad essere governata dal meccanismo CSS reflow.
I tre elementi orizzontali di un header mobile
Avendo messo in piedi la struttura generale scendiamo ora nel dettaglio dell’interfaccia per realizzare gli elementi orizzontali. Sia per iOS che Android troviamo una struttura molto simile composta da tre elementi principali :
- Tasto menu/indietro a sinistra
- Nome dell’app/vista corrente centrale
- Uno o più tasti di interazione a destra
A livello di struttura la situazione è simile alla precedente, il primo elemento a sinistra ha tendenzialmente una larghezza fissa, mentre l’area dei pulsanti a destra ha una larghezza variabile a seconda del numero di elementi che contiene, la zona centrale invece è a larghezza elastica ed occupa tutto lo spazio tra i due elementi fratelli.
La struttura HTML necessaria è simile alla precedente :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<nav id="top-bar"> <div class="top-bar-left"> left </div> <div class="top-bar-middle"> <div class="top-bar-outWrap"> <div class="top-bar-inWrap"> <p class="app-name">middle</p> </div> </div> </div> <div class="top-bar-right"> <p class="action-overflow">right</p> <p class="action-overflow">right</p> </div> </nav> |
La differenza sta nel CSS, il contenitore principale #top-bar con display:table avrà come proprietà table-layout:auto in modo da consentire agli elementi figli in display:table-cell di adattarsi in base alla larghezza dei propri contenuti.
L’elemento centrale .top-bar-middle conterrà un’ulteriore struttura di div per consentire il corretto centramento verticale e contenimento orizzontale dei contenuti tramite position:absolute, in modo che la dimensione sia dettata dal padre in display:table-cell. Questo permetterà agli elementi testuali al suo interno aventi text-overflow:ellipsis di non eccedere mai il contenitore centrale e di non influenzare gli elementi circostanti.
La width:1% applicata all’elemento .top-bar-right serve a limitare la larghezza rispetto ai contenuti interni, i quali se disposti come tag separati dovranno avere impostato display:table-cell per poter essere affiancati orizzontalmente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
html, body, #top-bar { font: 16px Arial; height:100%; margin:0; padding:0; } #top-bar { display:table; table-layout:auto; > * { display:table-cell; position:relative; text-align:center; vertical-align:middle; } .top-bar-left { width:40px; } .top-bar-middle { vertical-align:top; } .top-bar-right { width:1%; .action-overflow { display:table-cell; } } } .top-bar-outWrap { display:table; height:100%; position:absolute; table-layout:fixed; width:100%; .top-bar-inWrap { display:table-cell; vertical-align:middle; width:100%; } } .app-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } |
In questo frangente non sono necessari accorgimenti JavaScript di alcun tipo in quanto le larghezze vengono calcolate senza problemi nei vari browser.
La barra dei comandi in basso allo schermo
Realizzare la barra dei comandi è relativamente semplice, sia in iOS che Android gli elementi che la compogono hanno una larghezza variabile che equivale alla larghezza del contenitore padre equamente distribuita tra i suoi figli :
1 2 3 4 5 6 7 8 9 |
<nav id="bottom-bar"> <div class="bottom-elm">one</div> <div class="bottom-elm">two</div> <div class="bottom-elm">tree</div> </nav> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
html, body, #bottom-bar { font: 16px Arial; height:100%; margin:0; padding:0; } #bottom-bar { display:table; width:100%; } .bottom-elm { border-right:1px solid black; display:table-cell; text-align:center; vertical-align:middle; width: 1%; &:last-child { border-right:none; } } |
Combinazione delle CSS table
Il risultato finale delle tre tecniche combinate ci restituisce una struttura flessibile sia in altezza che in larghezza, in cui i contenuti posso essere modificati di dimensione e numero senza rischiare di rompere nulla. Inoltre così facendo evitiamo di usare il position:fixed notoriamente instabile in diversi frangenti sui dispositivi mobili, e ci assicuriamo che la scrollbar dei contenuti corrisponda esattamente all’area dei contenuti stessi e non si estenda al resto della pagina.
con l’ultima versione di safari 10 il layout non funziona più.
non si vede nemmeno il pulsante per inviare il commento.
Ciao Luca,
grazie per la segnalazione, ho fatto delle prove e in effetti Safari 10 tratta diversamente due aspetti del CSS:
Riassumendo le aggiunte sono le seguenti :
Ho aggiornato sia gli snippet della pagina che gli esempi live su CodePen
Per il “tasto commento” ti riferisci invece al layout del blog? Puoi postarmi uno screenshot?