Kurs HTML i CSS · Moduł 4: Flexbox

Flex-grow, Flex-shrink, Flex-basis - precyzyjna kontrola rozmiaru

9 min czytania ·
W tej lekcji7

Witaj znów w Bibliotece Aleksandryjskiej! Na jednej półce leżą trzy zwoje: pierwszy zawsze potrzebuje tyle samo miejsca, drugi może zająć resztę, a trzeci ustąpi, gdy zrobi się ciasno, zupełnie jak menu, treść i panel na stronie. Decydują o tym flex-basis, flex-grow i flex-shrink.

Flex-basis - początkowy rozmiar elementu

flex-basis określa początkowy rozmiar elementu PRZED rozdzieleniem wolnego miejsca. Przyjmuje piksele, procenty, zero albo auto:

.item {
  flex-basis: 200px;  /* Początkowy rozmiar 200px */
}

.item {
  flex-basis: 0;  /* Ignoruj rozmiar treści, zacznij od 0 */
}

.item {
  flex-basis: auto;  /* Użyj rozmiaru treści (default) */
}

.item {
  flex-basis: 50%;  /* 50% kontenera */
}

Zero każe zignorować rozmiar treści. Domyślne auto bierze szerokość elementu, a gdy jej nie ma, rozmiar treści. Czym więc flex-basis różni się od width?

/* Width poza flexboxem jest sztywny */
.item {
  width: 200px;  /* Poza flexboxem zawsze 200px */
}

/* Flex-basis jest sugestią */
.item {
  flex-basis: 200px;  /* Około 200px, ale może rosnąć/kurczyć się */
}

Poza flexboxem width: 200px to zawsze 200 pikseli. W kontenerze flex obie wartości są punktem startowym: przy flex-basis: auto przeglądarka bierze width, a gdy ustawisz oba, wygrywa flex-basis. To jak blok kamienny przed rzeźbieniem, który można jeszcze powiększyć albo pomniejszyć.

Flex-grow - rosnięcie elementu

flex-grow określa, jak bardzo element może rosnąć w stosunku do innych, gdy zostaje wolne miejsce:

.container {
  display: flex;
  width: 600px;
}

.item-1 {
  flex-grow: 1;  /* Zajmie 1 część wolnej przestrzeni */
  flex-basis: 100px;
}

.item-2 {
  flex-grow: 2;  /* Zajmie 2 części wolnej przestrzeni */
  flex-basis: 100px;
}

.item-3 {
  flex-grow: 1;  /* Zajmie 1 część wolnej przestrzeni */
  flex-basis: 100px;
}

/* Wolna przestrzeń: 600px - 300px = 300px
   Suma flex-grow: 1 + 2 + 1 = 4

   item-1: 100px + (300px × 1/4) = 175px
   item-2: 100px + (300px × 2/4) = 250px
   item-3: 100px + (300px × 1/4) = 175px
*/

Wolne 300 pikseli przeglądarka dzieli na cztery części, bo tyle wynosi suma flex-grow. Uwaga: flex-grow: 2 nie robi elementu dwa razy większego, tylko daje mu dwa razy większy kawałek wolnego miejsca. Domyślne flex-grow: 0 oznacza brak wzrostu, 1 wzrost proporcjonalny, a 2 czy 3 dwa lub trzy razy większy udział. Zwykle rośnie tylko treść między panelami:

.layout {
  display: flex;
}

.sidebar {
  flex-grow: 0;  /* Sidebar NIE rośnie */
  flex-basis: 250px;  /* Sztywny rozmiar 250px */
}

.main-content {
  flex-grow: 1;  /* Main content zajmie całą pozostałą przestrzeń */
}

.aside {
  flex-grow: 0;  /* Aside NIE rośnie */
  flex-basis: 200px;
}

Menu i panel trzymają 250 i 200 pikseli, a treść z flex-grow: 1 zabiera całą resztę.

Flex-shrink - kurczenie się elementu

flex-shrink działa w drugą stronę: określa, jak bardzo element może się kurczyć, gdy brakuje miejsca. Domyślna wartość to 1:

.item {
  flex-shrink: 1;  /* Element może się kurczyć (default) */
}

.item-important {
  flex-shrink: 0;  /* Element NIE kurczy się */
}

.item-flexible {
  flex-shrink: 3;  /* Element kurczy się 3x szybciej niż inne */
}

Zero wyłącza kurczenie, a większa liczba oznacza większy udział w oddawaniu miejsca. Sprawdźmy to w kontenerze, w którym brakuje 200 pikseli:

.container {
  display: flex;
  width: 400px;
}

.item-1 {
  flex-basis: 200px;
  flex-shrink: 1;  /* Kurczy się normalnie */
}

.item-2 {
  flex-basis: 200px;
  flex-shrink: 2;  /* Kurczy się 2x szybciej */
}

.item-3 {
  flex-basis: 200px;
  flex-shrink: 0;  /* NIE kurczy się */
}

/* Razem: 600px, ale kontener ma tylko 400px
   Brakuje: 200px

   item-3 pozostanie 200px (flex-shrink: 0)
   item-1 i item-2 podzielą się kurczeniem proporcjonalnie
*/

Trzeci element zostaje przy 200 pikselach, a brakujące 200 oddają dwa pierwsze w proporcji 1 do 2: pierwszy kurczy się do około 133, drugi do około 67 pikseli. Przy kurczeniu liczy się też flex-basis, więc większe elementy oddają więcej. Najczęściej flex-shrink chroni elementy, które nie mogą się zgnieść, jak logo:

.header-logo {
  flex-shrink: 0;  /* Logo NIGDY się nie kurczy */
  width: 150px;
}

.header-nav {
  flex-shrink: 1;  /* Nawigacja może się kurczyć */
}

.header-search {
  flex-shrink: 2;  /* Wyszukiwarka kurczy się najszybciej */
}

Logo zawsze ma 150 pikseli, a gdy robi się ciasno, wyszukiwarka z flex-shrink: 2 oddaje miejsce chętniej niż nawigacja.

Flex shorthand - łączenie wszystkich trzech

Z poprzedniej lekcji znasz skrót flex, który łączy wszystkie trzy wartości w kolejności grow, shrink, basis:

/* flex: grow shrink basis */
.item {
  flex: 1 1 200px;
  /* flex-grow: 1, flex-shrink: 1, flex-basis: 200px */
}

Tak samo flex: 1 0 200px to flex-grow: 1, flex-shrink: 0 i flex-basis: 200px.

Najczęstsze wartości flex:

W codziennej pracy wystarczy kilka gotowych form, które warto znać na pamięć:

/* flex: 1 - elastyczny element */
.item {
  flex: 1;
  /* Równoważne: flex: 1 1 0 */
  /* Rośnij, kurcz się, zacznij od 0 */
}

/* flex: auto - naturalny rozmiar z elastycznością */
.item {
  flex: auto;
  /* Równoważne: flex: 1 1 auto */
  /* Rośnij, kurcz się, zacznij od rozmiaru treści */
}

/* flex: none - sztywny rozmiar */
.item {
  flex: none;
  /* Równoważne: flex: 0 0 auto */
  /* NIE rośnij, NIE kurcz się */
}

/* flex: 0 0 200px - absolutnie sztywny */
.item {
  flex: 0 0 200px;
  /* Zawsze 200px, bez rosnięcia/kurczenia */
}

/* flex: 2 - rośnij 2x szybciej */
.item {
  flex: 2;
  /* Równoważne: flex: 2 1 0 */
}

flex: 1 startuje od zera, więc kilka takich elementów dzieli miejsce po równo, a flex: auto startuje od rozmiaru treści, więc dłuższy tekst dostanie szerszą kolumnę. Moja rada: przy równych kolumnach wybieraj flex: 1.

Praktyczne przykłady

1. Dashboard layout - 3 kolumny

Pierwszy układ to panel administracyjny z menu, treścią i widżetami:

<div class="dashboard">
  <aside class="sidebar">Sidebar (stały)</aside>
  <main class="main-content">Main (elastyczny)</main>
  <aside class="widgets">Widgets (stały)</aside>
</div>

Menu dostaje sztywne flex: 0 0 250px, treść flex: 1, a panel flex: 0 1 300px, czyli woli 300 pikseli, ale może ustąpić:

.dashboard {
  display: flex;
  gap: 20px;
  height: 100vh;
}

.sidebar {
  flex: 0 0 250px;  /* Sztywny: 250px */
  background: #34495e;
}

.main-content {
  flex: 1;  /* Zajmie całą pozostałą przestrzeń */
  background: white;
  overflow-y: auto;
}

.widgets {
  flex: 0 1 300px;  /* Preferowany: 300px, może się kurczyć */
  background: #ecf0f1;
}

/* Na małych ekranach */
@media (max-width: 768px) {
  .dashboard {
    flex-direction: column;
  }

  .sidebar,
  .widgets {
    flex: 0 0 auto;  /* Automatyczny rozmiar */
  }
}

Na telefonie media query ustawia kolumny jedna pod drugą i przywraca panelom naturalny rozmiar przez flex: 0 0 auto.

2. Header responsywny

Drugi przykład to nagłówek z logo, nawigacją, wyszukiwarką i kontem:

<header class="header">
  <div class="logo">Logo</div>
  <nav class="nav">Nawigacja</nav>
  <div class="search">Wyszukiwarka</div>
  <div class="user">User</div>
</header>

Zwróć uwagę na min-width: 0 przy nawigacji i min-width: 150px przy wyszukiwarce, do których wrócimy przy debugowaniu:

.header {
  display: flex;
  gap: 20px;
  padding: 10px 20px;
  background: #2c3e50;
  align-items: center;
}

.logo {
  flex: 0 0 150px;  /* Zawsze 150px */
}

.nav {
  flex: 1 1 auto;  /* Zajmie dostępną przestrzeń */
  min-width: 0;  /* Ważne dla kurczenia się */
}

.search {
  flex: 0 1 300px;  /* Preferuje 300px, może się kurczyć */
  min-width: 150px;  /* Minimum 150px */
}

.user {
  flex: 0 0 100px;  /* Zawsze 100px */
}

Nawigacja zabiera wolne miejsce, a wyszukiwarka woli 300 pikseli, ale nie zejdzie poniżej 150.

3. Card grid z różnymi rozmiarami

Trzeci przykład to siatka kart w trzech rozmiarach:

<div class="card-grid">
  <div class="card small">Small</div>
  <div class="card medium">Medium</div>
  <div class="card large">Large</div>
  <div class="card small">Small</div>
</div>

Kontener zawija karty dzięki flex-wrap: wrap, czyli flex-flow: row wrap z poprzedniej lekcji. Małe karty biorą ćwierć szerokości minus część odstępu, średnie połowę, a duże cały rząd:

.card-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

.card {
  height: 200px;
  background: #3498db;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
}

.card.small {
  flex: 0 0 calc(25% - 15px);  /* 1/4 szerokości minus gap */
}

.card.medium {
  flex: 0 0 calc(50% - 10px);  /* 1/2 szerokości minus gap */
}

.card.large {
  flex: 0 0 100%;  /* Pełna szerokość */
}

@media (max-width: 768px) {
  .card.small,
  .card.medium {
    flex: 0 0 100%;  /* Pełna szerokość na mobile */
  }
}

Cztery małe karty wypełniłyby rząd co do piksela, bo cztery razy calc(25% - 15px) plus trzy odstępy po 20 pikseli daje 100%.

Debugowanie Flexbox

Problem 1: Czy width blokuje rosnięcie?

Wielu początkujących uważa, że width przy flex-grow blokuje rosnięcie. Oto taki kod i dwie popularne recepty:

/* Problem */
.item {
  flex-grow: 1;
  width: 200px;  /* To tylko punkt startowy */
}

/* Rozwiązanie */
.item {
  flex: 1;  /* flex automatycznie nadpisuje width */
  /* lub */
  flex-grow: 1;
  min-width: 200px;  /* Użyj min-width zamiast width */
}

To mit: w kontenerze szerokim na 600 pikseli dwa takie elementy mają po 300, bo width jest tylko punktem startowym. Kłopot pojawia się, gdy sąsiedzi mają różne szerokości, bo każdy startuje z innego miejsca, a flex: 1 wyrównuje ten start. Jeśli element naprawdę nie rośnie, sprawdź flex-grow, max-width i to, czy rodzic ma display: flex.

Problem 2: Element nie kurczy się

Ten problem jest prawdziwy: element z flex-shrink: 1 uparcie trzyma swoją szerokość:

/* Problem */
.item {
  flex-shrink: 1;
  min-width: 300px;  /* min-width zapobiega kurczeniu! */
}

/* Rozwiązanie */
.item {
  flex-shrink: 1;
  min-width: 0;  /* Pozwól na kurczenie */
}

Winne jest min-width. Elementy flex mają domyślnie min-width: auto, więc nie kurczą się poniżej swojej najdłuższej niełamliwej treści, na przykład adresu URL.

Problem 3: Tekst przepełnia kontener

Z tej domyślnej wartości wynika trzeci problem, czyli długi tekst wypychający element poza kontener:

/* Problem */
.item {
  flex: 1;
  /* Długi tekst może wypchnąć element poza kontener */
}

/* Rozwiązanie */
.item {
  flex: 1;
  min-width: 0;  /* Kluczowe! */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

min-width: 0 pozwala się skurczyć, overflow: hidden ucina nadmiar, a text-overflow: ellipsis dopisuje wielokropek do tekstu trzymanego w jednej linii.

Podsumowanie

flex-basis to elastyczny odpowiednik width, flex-grow z domyślnym 0 decyduje o rośnięciu, a flex-shrink z domyślnym 1 o kurczeniu. Skrót flex łączy wszystkie trzy. W kolejnej lekcji zbudujesz z tych klocków całą siatkę kolumn.

W edytorze poniżej czeka pokaz odstępów, do którego wrócimy przy gap. Dopisz regułę .flex-item:first-child { flex-grow: 1; } i zobacz, jak pierwszy element zabiera wolne miejsce sąsiadom.

Kod do tej lekcji: index.html
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flexbox - Gap</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>↔Gap - Odstępy między elementami</h1>
        <p class="subtitle">Nowoczesny sposób na tworzenie odstępów w flexbox</p>

        <section class="demo-section">
            <h2>Bez gap</h2>
            <div class="flex-container no-gap">
                <div class="flex-item">1</div>
                <div class="flex-item">2</div>
                <div class="flex-item">3</div>
                <div class="flex-item">4</div>
            </div>
            <p class="description">Elementy bez odstępów</p>
        </section>

        <section class="demo-section">
            <h2>gap: 10px</h2>
            <div class="flex-container gap-10">
                <div class="flex-item">1</div>
                <div class="flex-item">2</div>
                <div class="flex-item">3</div>
                <div class="flex-item">4</div>
            </div>
            <p class="description">Mały odstęp między wszystkimi elementami</p>
        </section>

        <section class="demo-section">
            <h2>gap: 30px</h2>
            <div class="flex-container gap-30">
                <div class="flex-item">1</div>
                <div class="flex-item">2</div>
                <div class="flex-item">3</div>
                <div class="flex-item">4</div>
            </div>
            <p class="description">Duży odstęp między elementami</p>
        </section>

        <section class="demo-section">
            <h2>row-gap i column-gap oddzielnie</h2>
            <div class="flex-container custom-gap">
                <div class="flex-item">1</div>
                <div class="flex-item">2</div>
                <div class="flex-item">3</div>
                <div class="flex-item">4</div>
                <div class="flex-item">5</div>
                <div class="flex-item">6</div>
            </div>
            <p class="description">row-gap: 40px, column-gap: 10px</p>
        </section>

        <section class="demo-section practice">
            <h2>Praktyka: Egipska plansza Senet</h2>
            <p class="practice-description">Gra Senet - najstarsza gra planszowa z Egiptu</p>

            <div class="board-container">
                <div class="senet-board">
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                    <div class="square empty"></div>
                    <div class="square">●</div>
                    <div class="square">●</div>
                </div>
            </div>

            <div class="game-info">
                <h3>ℹO grze Senet:</h3>
                <ul>
                    <li>Najstarsza znana gra planszowa (ok. 3100 p.n.e.)</li>
                    <li>Gra dla 2 osób</li>
                    <li>Plansza 3x10 pól</li>
                    <li>Ulubiona gra faraonów</li>
                    <li>Znaleziona w grobach egipskich</li>
                </ul>
            </div>
        </section>
    </div>
</body>
</html>

Pamiętaj: flex-grow, flex-shrink i flex-basis to rozkazy faraona dla budowniczych piramid, które mówią, które bloki mają urosnąć, które się zmniejszyć, a które pozostać nietknięte.

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co robi właściwość flex-grow?

  2. 2. Właściwość skrótowa flex: 1 0 200px odpowiada jakiemu zestawowi wartości?

Zadania praktyczne w grze

  • Układanie w pionie

    Ułóż wartości flex-grow od najmniejszego do największego wzrostu:

  • Układanie w poziomie

    Ułóż skrótowy zapis flex-flow ustawiający poziomy kierunek z zawijaniem elementów:

  • Klikanie w kolejności

    Ułóż elementy aby stworzyć poprawną deklarację flex-basis:

  • Układanie w poziomie

    Ułóż kolejność wartości we właściwości skrótowej flex:

Przydatne artykuły