Flexbox senza segreti: i flex-items

Nello scorso articolo abbiamo parlato dei concetti fondamentali per capire il flexbox layout, focalizzando l’attenzione sul container. Oggi completiamo la guida e sveliamo i segreti dei flex-items, i children del container flexbox, capendo finalmente alcune regole ostiche come flex-grow o flex-shrink.

Giu 10, 2024 | Web Development

Benvenuti in questa seconda parte della guida al flexbox layout.

Mentre nello scorso articolo abbiamo approfondito il container flex, oggi affronteremo le regole principali dei flex-items, i children del flex container, cercando di spiegarne il funzionamento e poter finalmente utilizzarle per creare il nostro layout.

Ripasso

In questo articolo farò riferimento ad alcuni concetti già esposti nell’articolo sul container, come lo schema del W3C e relative dimensioni o le regole del container che ovviamente hanno effetto sui children. Se durante la lettura trovate un termine poco chiaro potete consultare l’articolo qui:

Codice iniziale

Come sempre, il modo migliore per imparare le regole del CSS è applicarle. Per questo partiamo da una situazione di base che comprende un container e tre flex-items, con alcune regole CSS di base:

<section class="flex-container">
  <div class="flex-item">1</div>
  <div class="flex-item">2</div>
  <div class="flex-item">3</div>
</section>
.flex-container {
  /*Regole per centrare il container rispetto al body*/
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  
  /*Un po' di stile per capire meglio il risultato*/
  background: #d6abab;
  padding: 20px;
  
  /*Regole del flexbox*/
  display: flex;
}

.flex-item {
  /*Regole per centrare i numeri*/
  display: flex;
  justify-content: center;
  align-items: center;
  
  /*Stile dei flex-item*/
  width: 100px;
  height: 100px;
  background: #441732;
  color: white;
  font-size: 1.5rem;
  font-family: sans-serif;
  /*Evidenziare i singoli item*/
  outline: 2px dotted;
  box-sizing: border-box;
}

Nel codice ho aggiunto dei commenti descrittivi: alcune proprietà le ho inserite solo per impostare il progetto e non riguardano il flexbox layout in senso stretto e saranno di conseguenza tralasciate nel ragionamento.
Concentrandoci invece su quelle rilevanti, notiamo che abbiamo banalmente un container flex senza width con un padding di 20px che racchiude tre flex-items, ciascuno con width e height 100px, numerati da 1 a 3. A questi ultimi ho aggiunto l'outline bianco per individuarli con più facilità, dato che il container non ha gap. Questa scelta ha un motivo ben preciso: lo capiremo quanto entreremo nel vivo della trattazione.

Visivamente abbiamo questo risultato:

Figura 1 - Situazione iniziale del nostro esempio.

Le tre proprietà fondamentali

Per controllare la dimensione e le caratteristiche flex dei flex-items ci occuperemo di 3 proprietà fondamentali: flex-grow, flex-shrink e flex-basis. La combinazione dei valori assegnati a queste proprietà e la comprensione del loro funzionamento è la chiave per capire come gestire i nostri flex-items.

Il contesto è questo: il concetto di flessibilità nel flexbox layout si può leggere e interpretare come controllo del comportamento dei div a seconda dello spazio disponibile. Noi abbiamo bisogno di strumenti per determinare cosa succederà ai nostri children al variare dello spazio del container, se questo si riduce o cresce. Capite che è un approccio molto responsive-friendly e questo esplicita chiaramente le motivazioni per cui flexbox è stato introdotto.

Analizziamo singolarmente le tre proprietà elencate. Si può dire che ciascuna si occupa di uno specifico aspetto del comportamento del flex-item, riassumibile con una domanda:

  • flex-grow definisce la capacità del flex-item di crescere, di “occupare spazio”. Risponde alla domanda: se lo spazio del container aumenta, come si comporterà il flex-item?
  • flex-shrink è l’opposto del flex grow e gestisce la capacità dell’item di restringersi se necessario. Quindi risponde alla domanda: se lo spazio disponibile nel container si riduce, come si comportano i flex-item?
  • flex-basis è un po’ più complicato da capire rispetto agli altri due. Gestisce la dimensione di riferimento che possiamo assegnare al nostro flex-item e sulla quale poi il layout flexbox si baserà per distribuire lo spazio. In poche parole risponde alla domanda: quale è la dimensione di base (appunto) da cui il CSS calcola la distribuzione dello spazio?
aldo giovanni giacomo pignolo
Consideriamo il main axis
Quando parlo di spazio rimanente, dimensioni e variazione sto considerando il main axis. Valgono tutte le considerazioni fatte nello scorso articolo: le proprietà che stiamo analizzando considerano di default il main axis orizzontale, ma nulla vieta, applicando le opportune regole, di cambiare l’orientamento del main axis, rendendolo verticale. In tal caso anche il comportamento delle proprietà dei flex-item sia datterà al nuovo contesto. Una cosa è sicura: non stiamo parlando del cross-axis.

Anche se per ora c’è ancora un po’ di confusione, stiamo piano piano sbrogliando il bandolo della matassa. Ora passiamo in rassegna nel dettaglio ogni proprietà e con degli esempi capiamo il loro funzionamento pratico. Attenzione: ci sarà della matematica.

Flex-grow

Dicevamo che flex-grow gestisce la capacità del flex-item di espandersi, di occupare lo spazio in eccesso. Questo significa che se il container si allarga rispetto alla sua dimensione di default, allora il flex-item dovrà reagire in un certo modo a questo cambiamento. La dimensione di default del container è lo spazio minimo che occupa il container per contenere i children.

Riferendoci all’esempio iniziale, il container non ha width e non ha gap, quindi la sua larghezza (ricordiamoci, considero il main axis orizzontale), escludendo il padding, è di 300px. Questo perché contiene 3 item ciascuno di 100px di width.

Ipotizziamo di allungare il container a 600px in questo modo:

.flex-container {
  [...]
  width: 600px;
}

Dopo aver applicato la nuova width, ci accorgiamo che non succede niente ai flex-item. Il motivo è semplice: flex-grow ha un valore di default che è 0.

Figura 2 - Dopo aver cambiato la width del container a 600px, questo è il risultato

I valori di flex-grow

Prima di andare avanti capiamo cosa significa che flex-grow è uguale a 0. La proprietà può assumere dei valori numerici che indicano quale sarà la proporzione di spazio in eccesso che un flex-item potrà occupare in caso di variazione in positivo del container.

La situazione più semplice è quella di default, ovvero flex-grow: 0. Significa che i flex-item non si adatteranno e rimarranno così come sono; in altre parole non occuperanno altro spazio oltre quello che occuperebbero normalmente.

Se flex-grow fosse maggiore di 0, allora la situazione cambierebbe e lì subentrerebbero le proporzioni.

Innanzitutto andrà considerato il valore del flex-grow di tutti i flex-items siblings, ovvero appartenenti allo stesso container. Ciascuno avrà un proprio valore, implicitamente 0 se non è assegnata la proprietà.
Nel caso in cui il container aumenti la propria larghezza, lo spazio in eccesso risultante dalla differenza tra la larghezza finale e iniziale andrà suddiviso in proporzione tra tutti i flex-items a seconda del valore di flex-grow. Ragionando con le frazioni, la differenza sarà suddivisa in N parti date dalla somma dei valori di flex-grow e ogni flex-item acquisirà X parti. X è il valore di flex-grow.

Riprendiamo il nostro esempio che sicuramente è più chiaro di questa descrizione.
Abbiamo un container di 300px, sempre escludendo il padding, perché ogni item è di 100px. Se aumentiamo a 600px la width del container come stavamo facendo prima, ai flex-item non succederà niente perché il valore di flex-grow è 0.

Ma se noi assegnassimo al primo flex-item flex-grow: 1, allora tale item si allargherebbe occupando tutto lo spazio rimanente.

[...]

.flex-item:first-child {
  flex-grow: 1;
}

Il calcolo che andremmo a fare è questo:

600px - 300px = 300px

→ spazio in eccesso dopo la variazione di width

1 + 0 + 0 = 1

→ La somma dei valori di flex-grow dei tre flex-items corrisponde al numero di parti un cui andrà diviso lo spazio in eccesso, in questo caso solo 1.

300px * 1 = 300px

300px * 0 = 0

300px * 0 = 0

→ Lo spazio in eccesso sarà distribuito proporzionalmente ai valori di flex-grow.

Quindi il primo flex-item potrà occupare il 100% dello spazio in eccesso diventando di conseguenza 400px (100px di width di base + 300px). Il container di 600px sarà composto dal primo flex-item con width 400px, il secondo e il terzo di 100px ciascuno.

Figura 3 - Con la width del container impostata a 600px, assegnando flex-grow: 1 al primo flex-item questo occuperà tutto lo spazio in eccesso.

Tutti gli item con flex-grow: 1

Cosa succederebbe se impostassimo flex-grow: 1 anche negli altri due flex-items? Seguendo lo schema di calcolo appena fatto cambierà solo la proporzione: il numero di parti in cui sarà suddiviso lo spazio in eccesso sarà 3 (1+1+1), quindi i 300px di differenza tra lunghezza del container finale e iniziale saranno divisi per 3 e ciascun item si potrà allargare di 100px ciascuno. In parole povere, lo spazio in eccesso sarà suddiviso equamente.

.flex-item {
  [...]
  flex-grow: 1;
}

Ripetendo tutti i calcoli, la situazione sarebbe la seguente:

600px - 300px = 300px

→ spazio in eccesso dopo la variazione di width

1 + 1 + 1 = 3

→ Diversamente da prima, la somma dei valori di flex-grow è uguale a 3. Quindi lo spazio in eccesso andrà suddiviso in 3.

300px * 1/3 = 100px

300px * 1/3 = 100px

300px * 1/3 = 100px

→ Lo spazio in eccesso sarà distribuito proporzionalmente ai valori di flex-grow. Dato che ciascun item ha 1 porzione su 3, allora ciascuno si allargherà di 1/3 dello spazio in eccesso.

Otterremo così 3 flex-items ciascuno di larghezza 200px (100px iniziali + 100px dopo la distribuzione dello spazio in eccesso).

Figura 4 - Con la width del container impostata a 600px, assegnando flex-grow: 1 a tutti i flex-items, lo spazio in eccesso sarà suddiviso equamente.

Flex-shrink

flex-shrink è il corrispettivo di flex-grow ma per lo spazio in difetto. Se con flex-grow abbiamo sempre parlato del container che si allarga, che aumenta la propria main size, con flex-shrink consideriamo la situazione opposta, ovvero il container che si restringe e diminuisce la propria main size.

La teoria però non cambia. Tipologicamente il comportamento dei flex-items è lo stesso tra flex-grow e flex-shrink, solo opposto. Vediamo nello specifico quali sono somiglianze e differenze.

La principale somiglianza tra le due proprietà è la tipologia di valori assegnabili: anche flex-shrink accetta solo valori numerici.

La prima differenza importante invece è che il valore di default di flex-shrink è 1. Ciò significa che il flex-item ha la capacità di restringersi al diminuire della dimensione della main size. Non solo: tutti i flex-item di un container, avendo lo stesso valore di flex-shrink, si restringeranno in proporzioni uguali, quindi lo spazio in difetto sarà eliminato in modo uguale da ciascun item.

La seconda differenza è quella che ho appena scritto. Mentre con flex-grow lo spazio in eccesso sarà distribuito e aggiunto ai flex-item, con flex-shrink lo spazio in difetto (il risultato della differenza tra la lunghezza della main size iniziale e quella finale) sarà sottratto dai flex-item, in modo proporzionale al valore della proprietà flex-shrink assegnata a ciascun item.

Riprendiamo il nostro provvidenziale esempio iniziale. Il container non ha la proprietà width, quindi la sua larghezza sarà uguale alla somma delle width dei singoli flex-item, 300px. Ipotizziamo di diminuire questa dimensione assegnando al container width: 150px. Manteniamo i valori di flex-shrink degli item di default, ovvero 1.

.flex-container {
  [...]
  width: 150px;
}

I calcoli saranno molto simili a quelli precedenti, con la sottrazione al posto dell’addizione.

300px - 150px = 150px

→ spazio in e dopodifetto la variazione di width

1 + 1 + 1 = 3

→ La somma dei valori di flex-shrink data da ciasucn item è uguale a 3. Quindi lo spazio in difetto andrà suddiviso in 3.

150px * 1/3 = 50px

150px * 1/3 = 50px

150px * 1/3 = 50px

→ Lo spazio in difetto sarà distribuito proporzionalmente ai valori di flex-shrink. Dato che ciascun item ha 1 porzione su 3, allora ciascuno si restringerà di 1/3 dello spazio in difetto, sottraendo quella quantità alla sua larghezza.

100px - 50px = 50px

100px - 50px = 50px

100px - 50px = 50px

→ La nuova larghezza di ciascun flex-item sarà 50px, data dalla larghezza di base, 100px, meno la porzione di spazio in difetto assegnata.

Figura 5 - Impostiamo la width del container a 150px, sapendo che il valore di default di ogni flex-item è flex-shrink: 1. Lo spazio in difetto sarà equamente sottratto da ogni item.

La realtà è più complessa

Il caso appena esposto ha avuto una premessa fondamentale: tutti i flex-item sono della stessa width, 100px. Quindi la proporzione è più facile da fare, basta basarsi sui valori di flex-grow e flex-shrink.

Purtroppo la realtà dei fatti è più complicata.

Se le width fossero state diverse, allora la distribuzione dello spazio in eccesso o in difetto sarà proporzionale anche sia alla width che ai valori di flex-grow o flex-shrink a seconda di riduzione o estensione della main size. Senza contare che subentra anche la flex-basis che vedremo nel prossimo paragrafo.

Non voglio addentrarmi in calcoli più complicati di quelli già esposti. Mi limito a spiegare come funzionano i flex-items e come possiamo gestirli per i nostri layout senza fogli di calcolo Excel.

Flex-basis

Flex-basis è una proprietà dei flex-item che definisce la dimensione iniziale di riferimento di un item flessibile. Va da sé che a differenza delle due precedenti proprietà flex-basis accetterà gli stessi valori di width, siano essi in pixel, in percentuale o max-content/min-content, ma non numeri senza unità di misura.

Per ricordarne il significato si può fare riferimento alla parola basis: infatti la lunghezza che assegniamo a flex-basis sarà la base su cui verranno calcolati flex-grow e flex-shrink. Con una particolarità fondamentale: flex-basisfunziona in assenza di variazione di lunghezza del main axis del container. Non serve dunque cambiare questa dimensione per vedere gli effetti della proprietà.

Esempio per flex-basis

Prendiamo il solito, bellissimo e funzionale esempio iniziale. Stavolta però non cambiamo nessuna width: il container rimarrà di 300px. Cosa succederebbe se noi aggiungessimo al primo flex-item la proprietà flex-basis: 200px?

Premessa fondamentale: ricordiamoci che di default flex-grow: 0 e flex-shrink: 1 per ogni flex-item.

Seguitemi nel ragionamento.
Impostando flex-basis: 200px il nostro container rimarrebbe di 300px, però il primo item si comporterebbe come se la sua lunghezza fosse di 200px in un container “ristretto”. Per capire il motivo consideriamo il valore di flex basis come se fosse l’effettiva lunghezza dell’item: il container sarebbe lungo quindi 200px + 100px + 100px = 400px. Ma il nostro container misura 300px. Quindi assegnare flex-basis 200px al primo item significa dirgli “comportati come se la tua lunghezza fosse di 200px in un container ridotto da 400px a 300px".

Nel nostro esempio i flex-item si comporterebbero come se ci fosse un difetto di spazio di 100px: il primo flex-item occuperà 150px, il secondo e il terzo entrambi 75px. La distribuzione in questo caso avviene in proporzione alla larghezza dei flex-item e non al flex-shrink.

Figura 6 - Applicando flex-basis: 200px al primo flex-item, questo sarà gestito dal layout flexbox come se la sua larghezza fosse 200px, lasciando inalterata la larghezza del container.

Flex-basis non è uguale a width

Dopo quanto detto, verrebbe da pensare che flex-basis e width siano due proprietà equiparabili e intercambiabili. Non è così.

Flex-basis è un comando per dire al CSS di trattare quell’item come se avesse (per astrazione) quella determinata dimensione nel sistema del flexbox layout. Width invece cambia semplicemente la larghezza del flex-item ed è una proprietà che non fa parte del flexbox layout, o perlomeno non è specifica per quel tipo di approccio.

Possiamo notare la differenza sempre con il nostro esempio iniziale che ormai conosciamo bene.

La main size è sempre di 300px. Consideriamo il primo flex-item. Avremo due scenari:

  • Assegnando width: 200px al primo flex-item, il container si allargherà e diventerà 400px (200px + 100px + 100px);
  • Assegnando flex-basis: 200px, il container rimarrà di 300px, ma verrà gestito come se fosse stato ridotto da 400px a 300px. Il primo flex-item si comporterà come se avesse una larghezza di 200px quindi si restringerà in proporzione al totale e agli altri due. Questi ultimi si restringeranno di conseguenza. In soldoni avremo 150px + 75px + 75px, perché i 100px teorici in eccesso saranno distribuiti in proporzione 2/4, ¼ e ¼ ai rispettivi item.
Figura 7 - Applicando width: 200px e flex-basis: 200px al primo flex-item otterremo due scenari diversi.

Flex

La proprietà flex non è altro che uno shortcut delle proprietà precedenti. Assegnabile a un flex-teim, si scrive nella forma:

[...]

.flex-item:first-child {
  flex: 0 1 200px;
}

I primi due valori sono numerici e corrispondo rispettivamente a flex-grow, uguale a 0 nel nostro caso, e flex-shrink, di valore 1. Il terzo è la flex-basis, a cui abbiamo assegnato il valore 200px come esempio. Sic et sempliciter.

Conclusioni

Siamo giunti alla fine di questa guida in due parti sul flexbox layout. Ho cercato di andare dritto al sodo, per capire il funzionamento dei principali concetti del flexbox e avere le informazioni di base per applicarli. Insieme a grid si tratta di uno dei sistemi più usati per strutturare i propri div.

Non preoccupatevi se qualcosa vi sfugge o non avete capito qualche passaggio: per questo esistono i professionisti che conoscono questi argomenti e sanno come gestirli e applicarli al vostro sito web.

Se state pensando di investire in un nuovo sito web o semplicemente fare un restyling al sito che avete già, contattatemi e sarò felice di aiutarvi a costruire il vostro nuovo quartier generale digitale.

Alla prossima!