Zrozumieć Flexboxa

Właściwości dziecka

Drogi Czytelniku, witaj w trzecim rozdziale. Bardzo się cieszę, że tutaj dotarłeś - to była solidna dawka wiedzy. Po przeczytaniu poprzednich rozdziałów powinieneś wiedzieć jak włączać pozycjonowanie na flexie, jak zmieniać kierunek układania elementów, oraz kierunki zawijania linii.

Pojęcie osi głównej/podstawowej czyli main-axis, czy osi przeciwstawnej cross-axis też nie powinno być Ci obce. Podobnie jak pozycjonowanie elementów względem tych osi.

Jeśli nie czujesz się swobodnie z wyżej wymienionymi zagadnieniami - zawsze możesz wrócić do poprzednich rozdziałów.

Wszystkie te nowopoznane właściwości ustawiamy oczywiście na rodzicu - kontenerze, wewnątrz którego elementy chcemy pozycjonować na flexie. Jednak elementy znajdujące się wewnątrz kontenera też posiadają swoje właściwości, które wpyłają na ich rozmiar i położenie.

Przykład, do którego doszliśmy w poprzednim rozdziale wyglądał nastepująco:

Align-self

Czasami zdarza się, że chcemy aby pojedyncze elementy były wypozycjonowane nieco inaczej. Możemy indywidualnie ustawiać ułożenie poszczególnych elementów względem cross-axis przy pomocy align-self.

Właściwość align-self, aby zadziałała, musi zostać nadana na dzieciach pozycjonowanych na flexie. Przyjmuje ona właściwości takie same jak align-items, czyli flex-start, flex-end, center, baseline oraz stretch. Domyślnie jednak ustawiona jest na auto - oznacza to, że elementy podporządkowują się układowi nadanemu przez rodzica.

Na jednym z naszych znaczników o klasie .card ustawimy więc właściwość align-self, np. na stretch

Jak widzisz, wszystkie elementy są ułożone standardowo, czyli są wycentrowane - natomiast karta o numerze 5 jest rozciągnięta na całą wysokość linii flexboxa. Dzieje się tak, ponieważ na selektorze .card-5 ustawiliśmy align-self: stretch;

Karcie numer 3 nadajmy natomiast align-self na flex-start a karcie .card-1 - flex-end.

Jak widzisz, daje nam to dużą elastyczność w tworzeniu layoutu, jeśli specifyczny element wewnątrz kontenera powinien zachowywać się inaczej - tzn. być inaczej wypozycjonowany.

To pierwsza z właściwości, które nadajemy na dzieciach flexboxa. Kolejną z nich jest właściwość order.

Order

Z angielskiego, order oznacza kolejność - i to właśnie kolejnością możemy dzięki tej właściwości zarządzać. Domyślnie elementy układają nam się kolejno jeden po drugim, w kierunku zdefiniowanym przez flex-direction. To znaczy, że elementy mogą być rysowane od prawej strony, od dołu, bądź na wiele innych sposobów, jednak zawsze układane są jeden po drugim.

Używając właściwości order, możemy zmienić położenie konkretnego elementu. Nie chodzi tutaj o jego wizualne położenie, np. przesunięcie w prawo, tylko właśnie kolejność wyświetlania. Właściwość order przyjmuje jako wartość liczbę całkowitą - domyślnie, wszystkie elementy posiadają order: 0. W przypadku, gdy każdy element ma tę samą wartość order, wyświetlają się zgodnie z kodem HTML strony, w kierunku zdefiniowanym przez flex-direction.

Natomiast nadając jednej z kart np. order: -1; sprawimy, że wyświetli się ona tak, jak gdyby znajdowała się jako pierwsza w tzw. "markupie".

Jak widzisz, karta .card-5 wyświetla się teraz jako pierwsza, pomimo tego, że w kodzie HTML jest 5 z kolei. W tym momencie warto zaznaczyć dwa fakty:

  1. Z uwagi na dostępność stron WWW musimy dalej dbać o kolejność znaczników - powinna ona odpowiadać jedynie za wizualne położenie znaczników, a nie ustawiać poszczególne elementy strony w logiczną całość.
  2. Właściwość order nie ustala konkretnej pozycji w w kodzie HTML

Jeśli nadamy order: 4; na karcie .card-2, nie oznacza to, że ta karta wyświetli się jako czwarta.

Kolejność wyświetlania jest ustalana jest rosnąco, od wartości najmniejszej do największej. To znaczy, że w tym przypadku pierwszą kartą, która wyświetli się będzie .card-5, następnie będą wszystkie karty, na których nie zdefiniowaliśmy konkretnej kolejności, a na samym końcu pojawi się .card-2.

Stanie się tak, ponieważ karty będą wyświetlane właśnie w kolejności rosnącej, czyli:

  1. order: -1; - karta .card-5
  2. order: 0; - domyślna wartość, karta .card-1
  3. order: 0; - domyślna wartość, karta .card-3
  4. order: 0; - domyślna wartość, karta .card-4
  5. order: 0; - domyślna wartość, karta .card-6
  6. order: 4; - karta .card-2

Ciekawe, prawda? Zobacz kolejny przykład:

Jeśli na karcie .card-6 również ustawimy order: 4;, kolejność będzie wyglądała następująco:

  1. order: -1; - karta .card-5
  2. order: 0; - domyślna wartość, karta .card-1
  3. order: 0; - domyślna wartość, karta .card-3
  4. order: 0; - domyślna wartość, karta .card-4
  5. order: 4; - karta .card-2
  6. order: 4; - karta .card-6

Jak już wspomniałem, w przypadku, kiedy wartość order jest równa na wielu elementach, ich pierwszeństwo jest definiowane poprzez kolejność w kodzie HTML.

Pamiętaj o dwóch wspomnianych wyżej zasadach i to w sumie wszystko, co musisz wiedzieć o order ;)

Flex-shrink

Jak pewnie zauważyłeś, ustawiliśmy flex-wrap na domyślną wartość no-wrap i nasze karty zwężają się tak, aby każda z nich zmieściła się w jednym rzędzie. Efekt ten jest kontrolowany przez flex-shrink.

Właściwość ta definiuje jak bardzo dany element ma się kurczyć w momencie, kiedy elementy nie mieszczą się już w kontenerze względem cross-axis.

Wartością, którą możemy nadać flex-shrink jest liczba większa lub równa 0, a jego wartością domyślną jest 1. Jeśli pozostawimy tą właściwość niezmienioną na wszystkich elementach bądź ustawimy dla każdego z nich taki sam flex-shrink, każda karta będzie kurczyła się równomiernie.

To znaczy, że elementy przy kurczeniu się będą się proporcjonalnie pomniejszać, tak, by zmieściły się w kontenerze.

Działanie flex-shrink możemy wyłączyć poprzez nadanie flex-shrink: 0; na dzieciach flexboxa.

Na karcie .card-3 ustawmy więc flex-shrink: 0; :

Jak widzisz, w momencie kiedy karty nie mogą mieć już swojej pełnej szerokości skalują się, oprócz karty numer 3. Niezależnie jak bardzo zwężymy okno przeglądarki, .card-3, zachowa swoją naturalną szerokość.

Jeśli ustawimy flex-shrink: 0; na każdej z naszych kart, czyli na .card, efekt będzie następujący:

Jak widzisz, żaden z elementów nie kurczy się, nawet jeśli nie ma na nie miejsca, co jest również domyślnym zachowaniem elementów blokowych ze stałą wysokością i szerokością, gdy nie używamy flexboxa w tworzeniu layoutów.

Więc flex-shrink: 1; uruchamia kurczenie się dane elementu. Wróćmy do sytuacji, kiedy elementy domyślnie kurczyły się.

Teraz jeśli na naszym elemencie .card-5 ustawimy flex-shrink: 2; będzie on kurczył się dwa razy bardziej niż elementy które mają na sobie domyślne flex-shrink: 1;. Sprawdź w narzędziach developera jak zmieniają się rozmiary danych elementów w momencie, gdy zwężasz okno przeglądarki.

Jeśli lubisz cyferki, poniżej postaram się przedstawić Ci jak dokładnie jest to obliczane:

Dla przykładu: domyślnie nasze karty mają ustawione width: 100px;. Jest to bazowa szerokość naszej karty. Dodatkowo, ustawiamy margines ze wszystkich stron na 5px poprzez margin: 5px;. To znaczy, że jedna karta zajmuje nam 110px. Jako, że mamy 6 kart, oznacza to, że gdy nasze okno jest węższe niż 660px, wtedy flex-shrink zaczyna działać.

Jako że .card-5 ma wartość kurczenia ustawioną na 2, będzie kurczył się dwa razy bardziej niż pozostałe elementy. Przykładowo, dla szerokości okna 432px karty z domyślnym flex-shrink: 1; będą miały szerokość 66px. Oznacza to, że skurczyły się one o 100px - 66px = 34px. Natomiast szerokość .card-5 wynosić będzie 32px, czyli karta skurczyła się o 100px - 32px = 64px. Jak widzisz, jest to dwukrotność wszystkich pozostałych kart.

Zobaczmy jeszcze jeden przykład. Na kartach .card-2 i .card-6 ustawimy flex-shrink: 5.

Poeksperymentuj z różnymi wartościami flex-shrink, tak długo aż będziesz czuł się komfortowo z tą właściwością. Wiedz też, że nie musi ona przyjmować jedynie liczb całkowitych - równie dobrze możesz ustawić flex-shrink: 1.3; na wybranych elementach.

Za to właśnie odpowiada flex-shrink - jest to współczynnik kurczenia się elementów, gdy nie mieszczą się one już w kontenerze. Oczywiście każde dziecko flex-boxa może mieć indywidualną wartość flex-shrink.

Flex-grow

Właściwością jakby przeciwną dla flex-shrink jest flex-grow, ponieważ odpowiada ona za zarządzanie rozmiarem elementów wtedy, kiedy dysponujemy nadmiarową przestrzenią w kontenerze na osi main-axis.

Podobnie jak flex-shrink, przyjmuje ona liczby rzeczywiste większe lub równe 0 oraz działa na zasadzie proporcji. Domyślnie, wszystkie dzieci kontenera, który wyświetla się przy pomocy display: flex;, mają ustawione flex-grow: 0;. Oznacza to, że domyslnie elementy ignorują nadmiarową przestrzeń.

Ustawmy na pierwszej karcie flex-grow: 1; oraz chwilowo włączymy zawijanie wierszy poprzez flex-wrap: wrap; na .card-section.

Jak widzisz, w linii flexa, w której obecnie znajduje się karta .card-1, przestrzeń, jaka do tej pory była dystrybuowana pomiędzy elementy za sprawą justify-content: space-between; jest zajęta przez kartę z numerem jeden.

Została ona poszerzona względem main-axis i zajmuje całe dostępne miejsce, respektując marginesy swoich bratnich elementów. Dzieje sie tak, bo jest to jedyny element, który posiada zdefiniowane flex-grow, które jest różne od zera.

Uprośćmy nasz przykład, pozbywając się reguł takich jak align-self, flex-shrink oraz order i ukryjmy wszystkie karty poza pierwszą, i drugą:

Teraz widać to jeszcze lepiej - pierwsza karta rozszerza się tak, by zajmować całą dostępną przestrzeń w main-axis. Przestrzeń, która normalnie zostałaby rozdzielona między elementami dzięki justify-content, została przekazana karcie pierwszej.

Jeśli ustawimy flex-grow: 1; na selektorze .card-2 elementy te będą zajmowały równo połowę dostępnej przestrzeni i będą sobie równe:

Jeśli z kolei drugiej karcie nadamy flex-grow: 2;, będzie ona zajmowała dwa razy więcej przestrzeni niż .card-1:

Jednak nie oznacza to, że karta 2 będzie dwa razy większa niż karta pierwsza.

Jak to jest obliczane? Bazowo, każda z naszych kart ma szerokość 100px. Załóżmy, że okno podglądu ma 632px szerokości. Czyli przestrzeń, którą flex mógłby zarządzać wynosiłaby 632px - 2 * 100px = 432px.

Suma wszystkich wartości flex-grow wynosi w tym przypadku trzy - więc karta pierwsza zajmie 1/3 dostępnej przestrzeni, a karta druga 2/3. Jedna trzecia dostępnej przestrzeni to 432px / 3 = 144px. Z tym, że każda z naszych kart ma jeszcze zagwarantowany margin: 5px; na który flex-box nie może wpłynąć - dlatego od tej liczby musimy odjąć 10px, ponieważ musimy uwzględnić margines z obu stron.

Czyli 144px - 2 * 5px = 134px. O tyle właśnie została powiększona karta .card-1 w przedstawionym przykładzie. Jej szerokość wynosi 234px ponieważ bazowo karta miała 100px szerokości, czyli 100px + 134px = 234px.

Jeśli chodzi o kartę drugą - zajmuje ona 2x więcej przestrzeni, niż .card-1 - czyli zajmuje dodatkowe 2 * 134px = 268px. Początkowo miała 100px szerokości, dlatego też ma 268px + 100px = 368px.

Właśnie o to chodzi - w flex-grow i flex-shrink definiujemy stosunki pomiędzy elementami flexboxa. W przypadku flex-grow jest to stosunek przestrzeni którą elementy mogą zająć w main-axis, natomiast w flex-shrink - współczynnik kurczenia się kart, również w main-axis.

Przywróćmy widoczność pozostałych czterech kart i ustawmy flex-grow: 1; dla każdej z nich:

Jak widzisz, każda karta zajmuje całą dostępną przestrzeń - ponieważ flex-grow oblicza dostępną przestrzeń per linia, ustawiając taki sam współczynnik wzrostu na każdym elemencie nie sprawimy, że każda linia będzie miała taki sam układ.

Ważne jest również to, że w tym wypadku justify-content w ogóle nie zadziała - ponieważ bazuje ono na dostępnej przestrzeni w main-axis, a my zużywamy całą na rozszerzenie elementów.

Pamiętaj też, że linia nie musi oznaczać linii poziomej! W zależności od wartości flex-direction nasza main-axis może biec pionowo lub poziomo, a flex-grow i flex-shrink działają właśnie w main-axis. To znaczy, że zmieniając flex-direction na column elementy będą zajmowały całą dostępną przestrzeń w osi pionowej:

Natomiast rozciągnięcie w cross-axis możemy osiągnąć za pomocą właściwości align-items i align-content ustawionym na stretch, w momencie kiedy szerokość jest ustawiona na auto, co jest wartością domyślną.

Jeśli nie zdefiniujemy wysokości, kolumny będą zawijały się inaczej w zależności od wysokości kontenera, ponieważ main-axis biegnie teraz pionowo (pamiętaj, żeby sprawdzać myszką w podglądzie gdzie jest main axis i w którym kierunku biegnie).

I to już wszystko, co musisz wiedzieć o flex-grow! Pozstała już tylko jedna właściwość flexboxa, którą powinieneś poznać, aby dobrze manipulować layoutami - potem nauczymy się tzw. shorthands, czyli po prostu skróconych notacji, które ustawiają poszczególne właściwości flexa. Przejdźmy więc do flex-basis.

Flex-basis

Jak już wiesz, flexbox oprócz tego, że bazuje na wielu właściwościach, zarówno dziecka jak i rodzica, respektuje też wysokość, szerokość oraz marginesy elementów. Można powiedzieć w skrócie, że respektuje on box model.

To znaczy, że min-width / min-height bądź max-width / max-height będą respektowane przy flex-shrink oraz flex-grow.

Natomiast jeśli chodzi o ustalanie rozmiarów elementów - ich bazowych wielkości - to w przypadku flexa możesz używać własciwości flex-basis. Działa ona podobnie do width i height których musieliśmy używać do ustawiania bazowych rozmiarów naszych elementów, z drobnymi różnicami.

Najważniejszą i kluczową różnicą jest to, że flex-basis może ustalać szerokość elementu bądź jego wysokość w zależności od kontekstu, ponieważ ustawia on rozmiar elementu w main-axis.

Jako że nie mamy ustalonej szerokości i wysokości, elementy będą nam się rozciągały w cross-axis za sprawą stretch na align-items i align-content. Usuniemy więc chwilowo flex-grow z naszych elementów oraz zmieńmy flex-direction na row.

flex-basis może przyjmować wartości analogiczne do width i height - czyli można mu przekazać wartość procentową, wykorzystać dowolne jednostki jak px, vh, vw, rem i tak dalej - oraz używać słów kluczowych takich jak auto, które jest zresztą domyślną wartością dla flex-basis. Jego efekt widzisz w podglądzie.

Ustawmy flex-basis: 100px na każdej naszej karcie, czyli na selektorze .card:

Jak widzisz, każdy element ma już 100px szerokości. Jeśli natomiast zmienimy flex-direction na column, elementy będą miały maksymalnie 100px wysokości, ponieważ main-axis będzie biegła pionowo.

Dzięki temu, przy zmianie kierunku wyświetlania w kontenerze flexboxa, nie musimy każdorazowo zmieniać width na height - po prostu, flex-basis kontroluje rozmiar elementów w main-axis, co bardzo często jest ogromnym ułatwieniem.

Każda karta, każdy element flexa może mieć inny flex-basis. Wróćmy do flex-direction: row i uczyńmy nasz przykład bardziej urozmaiconym:

Jak widzisz, daje to ogromne możliwości. Na przykład karta o numerze 4 zawsze będzie jedynym elementem w rzędzie, jako że ustawiliśmy jej flex-basis na 100% szerokości kontenera.

A tak będzie wyglądał nasz przykład, jeśli zmienimy kierunek na kolumnowy:

Jak widzisz, wystarczy przełączenie jednej właściwości, aby zupełnie zmienić layout naszego przykładu.

Mówiąc ogólnie - flex-basis ustala jakie wymiary mają mieć elementy w main-axis, zanim zostaną dotknięte przez inne właściwości flex-boxa, takie jak flex-grow czy flex-shrink.

Oczywiście, jeśli ustawilibyśmy na jednej z kart flex-shrink: 0;, miałaby ona zagwarantowaną szerokość równą rozmiarowi zdefiniowanemu przez flex-basis.

Uff! Bardzo dużo właściwości, bardzo dużo nowej wiedzy! W pewnym sensie na tym polega trudność flexboxa - musimy ułożyć sobie w głowie jak dane właściwości ze sobą współgrają i zwracać uwagę na wszystkie czynniki. Potem pozostaje przyjemność jego wykorzystywania.

Kończąc już ten rozdział, powiem Ci tylko, że flex-basis nie działa na elementach flexa, które są wypozycjonowane absolutnie - wtedy musimy już kontrolować ich rozmiar za pomocą width bądź height.

To już wszystkie najważniejsze właściwości CSS Flexbox. Istnieją jeszcze wspomniane wcześniej shorthands, które upraszczają trochę nasz kod i są po prostu notacjami skróconymi do zapisywania poznanych dzisiaj reguł - shorthands omówię w kolejnym, krótkim rozdziale.

Na ten moment poeksperymentuj z różnymi kombinacjami poznanych właściwości.

Oto wszystkie właściwości które womówiłem w kursie do tej pory:

Właściwości kontenera / rodzica

  • display: flex / inline-flex - ustawia element jako kontener flexa
  • flex-direction - definiuje kierunek w którym zwrócone jest main-axis
  • flex-wrap - ustala, czy elementy mają być zawijane do następnych linii oraz ustala kierunek cross-axis
  • justify-content - zarządza dostępną przestrzenią w main-axis i pozycjonuje dzieci na jej podstawie
  • align-content - zarządza przestrzenią w cross-axis i pozycjonuje linie flex-boxa na jej podstawie
  • align-items - zarządza przestrzenią w cross-axis dla pojedynczej linii flexboxa

Właściwości elementu / dziecka flexboxa

  • align-self - ustala położenie elementu względem cross-axis w linii flex-boxa w której się znajduje
  • order - definiuje priorytet kolejności wyświetlania się danego elementu bez względu na pozycję w markupie
  • flex-shrink - ustala współczynnik kurczenia się danego elementu
  • flex-grow - ustala współczynnik wzrostu danego elementu
  • flex-basis - ustala bazowe wymiary elementu względem main-axis, zanim transformacje spowodowane przez inne właściwości flexa będą miały miejsce

W następnym rozdziale krótko umówimy jak możemy przyśpieszyć pisanie layoutów na flexie przy pomocy skróconych notacji - shorthands.

Koniec rozdziału trzeciego