chevron-left chevron-right

CSS Grid – ficzer który uwalnia kreatywność w tworzeniu layoutów

W świecie front-endu od jakiegoś czasu przebojem jest korzystanie z nowych funkcjonalności CSS takich jak CSS Grid Layout czy zmienne CSS. Z pomocą tego pierwszego możemy budować w sposób przewidywalny szablony stron czy aplikacji internetowych. Natomiast z pomocą drugiego ficzera, możemy tworzyć zmienne, które będą posiadały określone wartości, możliwe do wykorzystania w całym naszym kodzie CSS. Zasada działania jest podobna do zmiennych w preprocesorach SASS czy LESS.

W tym tekście skupię się na możliwościach jakie dostarcza nam CSS Grid Layout. Omówię pokrótce, co to jest, czym to się różni od CSS Flexbox Layout i starszych sposobów układania elementów na stronie. Na sam koniec, przedstawię część możliwości które zostały nam udostępnione dzięki temu narzędziu.

Co to jest CSS Grid Layout?

Jest to narzędzie do budowania layoutów stron internetowych i aplikacji internetowych bazujących na siatce linii poprzecznych i prostopadłych (jak na kartce od zeszytu w kratkę). Elementy można umieszczać w swego rodzaju komórkach, tutaj zwanymi grid-area. Szerokości kolumn i wysokości wierszy mogą być ustalane za pomocą różnych jednostek miar: pikseli, procentów czy nawet za pomocą miary nazwanej fr - czyli części pozostałego wolnego obszaru.

Jeśli dalej to brzmi dość enigmatycznie, to w dalszej części tekstu sporo niewiadomych zostanie wyjaśnionych.

Jaka jest różnica między CSS Grid a CSS Flexbox?

Jeśli wcześniej miałeś/miałaś okazję bawić się flexboxem, to możesz się pewnie zastanawiać jaka jest zasadnicza różnica między tymi dwoma sposobami budowania layoutów. Zasadniczo, różnice koncepcyjne są dwie:

  1. Flexbox jest jednowymiarowy, a Grid jest dwuwymiarowy - to oznacza, że stylując elementy z wykorzystaniem flexboxa, stylujesz tylko w jednej płaszczyźnie, tj. albo w pionie albo w poziomie. Natomiast w przypadku grida, mamy do czynienia z dwoma wymiarami: kolumnami i wierszami (pionem i poziomem).
  2. Flexbox jest zorientowany na content, czyli treść decyduje o tym ile miejsca potrzebuje element wykorzystujący model Flexbox. Natomiast, w przypadku grida o miejscu decyduje szablon jaki stworzyliśmy.

Implementacja menu za pomocą modelu Flexbox

Aby lepiej zobrazować punkt 2, to posłużę się przykładem. Załóżmy, że mamy listę z elementami (coś w rodzaju menu):

1
2
3
4
5
<nav class="menu">
    <a class="menu__item">Strona główna</a>
    <a class="menu__item">Kategorie</a>
    <a class="menu__item">Wyloguj się</a>
</nav>

Domyślnie kod wygląda następująco:

Widok menu bez Flexboxa i bez Grida

Zaimplementujmy jednak model Flexbox w elemencie o klasie menu. Zrobimy to w następujący sposób:

1
2
3
.menu {
    display: flex;
}

To poskutkuje następującym rezultatem:

Menu z zaimplementowanym modelem Flexbox bez pozycjonowania elementów wewnątrz

Gdybyśmy chcieli umieścić element z tekstem Wyloguj się na końcu kontenera, czyli po prawej stronie, to wystarczy dodać następujący kawałek kodu:

1
2
3
.menu__item:nth-child(3) {
    margin-left: auto;
}

Końcowy efekt wygląda następująco:

Menu z zaimplementowanym Flexboxem i umieszczeniem elementu na końcu kontenera

Implementacja menu za pomocą modelu Grid

W przypadku Grida kod CSS wygląda inaczej (co oczywiste). W gridzie możemy zdefiniować kolumny i wiersze. W tym przypadku, będziemy definiować tylko kolumny, a następnie określimy pozycję ostatniego elementu z listy menu:

1
2
3
4
5
6
7
8
.menu {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
}
 
.menu__item:nth-child(3) {
    grid-column: 5;
}

Końcowy efekt będzie wyglądał zupełnie tak samo jak na przykładzie z Flexboxem. Warto zwrócić uwagę na składnię: grid-template-columns: repeat(5, 1fr);. Dzięki tej własności możemy określić ile kolumn będzie miał w sobie element. W tym przypadku, będzie to 5 kolumn (może być ich dowolna liczba) i każda z kolumn będzie zajmowała dostępną wolną przestrzeń. W naszym przypadku, wykorzystanie jednostki miary: 1fr oznacza ułamek, czyli mamy 5 kolumn w kontenerze i przeglądarka automatycznie podzieli dostępną przestrzeń na 5 kolumn o równej szerości.

Gdybyśmy jednak użyli zapisu: grid-template-columns: 200px 30px repeat(3, 1fr); to oznaczałoby to, że mamy dalej 5 kolumn i pierwsza z nich ma szerokość 200 pikseli; druga ma szerokość 30 pikseli, a pozostałe 3 dzielą między sobą po równo dostępną pozostałą przestrzeń w kontenerze.

CSS Grid areas - ficzer, który zmienia sposób budowania layoutów

Kolumny i wiersze w CSS Grid można określać też w inny sposób niż za pomocą grid-template-columns i grid-template-rows. W specyfikacji istnieje ficzer, który można można określić mianem nazwanych obszarów/komórek. Jeśli założymy, że kolumny i wiersze zdefiniowane za pomocą własności wspomnianych na początku tego akapitu są anonimowe, to kolumny i wiersze określone za pomocą własności grid-template-areas będą już nazwanymi obszarami.

Jak to działa w praktyce, pokażę w następnej sekcji tego wpisu. Wcześniej jednak należy wyjaśnić dlaczego nazwane obszary są tak bardzo użyteczne:

  • Pozwalają na precyzyjne sterowanie miejscem, gdzie ma się pojawić dany element strony,
  • Porządkują nasz kod CSS; dzięki tej własności nie musimy się zastanawiać w którym miejscu znajduje dany element (bywa, że selektory CSS nie dają jednoznacznej odpowiedzi),
  • Unikamy zbędnego kodu HTML.

Być może lista argumentów powyżej daje Ci mgliste pojęcie jakie są korzyści z wykorzystania grid-template-areas, dlatego też przeanalizujmy następujący przykład, którym będzie przykładowy szablon strony internetowej:

Przykładowy szablon strony

Mamy widocznych 5 obszarów, z czego jeden nie zawiera treści. Mamy nagłówek, główną część strony, pasek boczny i stopkę. Możnaby by myśleć, że kod HTML jest dość rozbudowany, lecz prezentuje się banalnie:

1
2
3
4
5
6
<div class="grid">
    <div class="header">Header</div>
    <div class="main">Main</div>
    <div class="sidebar">Sidebar</div>
    <div class="footer">Footer</div>
</div>

Taki kod HTML został ostylowany następującym kodem 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
.grid {
    display: grid;
    grid-template-areas:
        'header header .'
        'main main sidebar'
        'footer footer sidebar';
    grid-template-columns: repeat(2, 1fr) 100px;
    grid-template-rows: repeat(3, 50px);
    grid-gap: 10px;
}
 
 
.header {
    background: blue;
    grid-area: header;
}
 
.main {
    background: yellow;
    grid-area: main;
}
 
.sidebar {
    background: pink;
    grid-area: sidebar;
}
 
.footer {
    background: lime;
    grid-area: footer;
}

W powyższym kodzie CSS są dwie rzeczy na którą należy zwrócić szczególną uwagę, czyli własności: grid-template-areas oraz grid-area. Za pomocą tej pierwszej własności zadeklarowanej w kontenerze, zawierającym nagłówek, stopkę, pasek boczny i część główną strony, z klasą .grid ustaliliśmy nazwane obszary. Dzięki nim, możemy w dalszej części wskazać które elementy będą przynaleć do którego obszaru. Zapis wartości właśności grid-template-areas może się wydawać osobliwy. Każdy wiersz jest osobną wartością tekstową, np. 'header header .'. Wierszy może być wiele. Dodatkowo, kropka oznacza obszar nieużytkowany. Nie będzie się dało w takie miejsce wstawić żadnego elementu strony.

Przynależność do określonego obszaru deklarujemy za pomocą grid-area, np. grid-area: main;.

W taki sposób można z łatwością budować szablony, struktury, co tylko zapragniemy. Co najważniejsze, elementy z tymi własnościami będą zawsze utrzymywane w ryzach dostępnego miejsca określonego dla danego obszaru.

Budujemy drabinkę pucharową za pomocą CSS Grid Layout

Przejdźmy do sedna, czyli do zbudowania drabinki pucharowej. Drabinki pucharowe są wizualizacją często stosowaną przy okazji mistrzostw w różnych dyscyplinach sportowych i nie tylko. Na potrzeby tego wpisu, zwizualizuję drabinkę Mistrzostw Europy w piłce nożnej z 2016 roku.

Drabinka pucharowa EURO 2016

Każdy z meczów znajdujących się w drabince pucharowej ma określoną strukturę HTML:

1
2
3
4
5
6
7
8
9
10
<div class="slot slot--18-1 slot--top slot--left">
    <div class="team team--home">
        <span class="team__name">Szwajcaria</span>
        <span class="team__score">1</span>
    </div>
    <div class="team team--away team--winner">
        <span class="team__name">Polska</span>
        <span class="team__score">1</span>
    </div>
</div>

Znajdziemy w nim miejsce, na nazwy drużyn i wynik spotkania. Zauważ, że korzystam z notacji BEM. Pełna lista meczów wygląda następująco:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<div class="tournament-tree">
    <div class="slot slot--18-1 slot--top slot--left">
        <div class="team team--home">
            <span class="team__name">Szwajcaria</span>
            <span class="team__score">1</span>
        </div>
        <div class="team team--away team--winner">
            <span class="team__name">Polska</span>
            <span class="team__score">1</span>
        </div>
    </div>
    <div class="slot slot--18-2 slot--left">
        <div class="team team--home">
            <span class="team__name">Chorwacja</span>
            <span class="team__score">0</span>
        </div>
        <div class="team team--away team--winner">
            <span class="team__name">Portugalia</span>
            <span class="team__score">1</span>
        </div>
    </div>
    <div class="slot slot--18-3 slot--top slot--left">
        <div class="team team--home team--winner">
            <span class="team__name">Walia</span>
            <span class="team__score">1</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Irlandia Północna</span>
            <span class="team__score">0</span>
        </div>
    </div>
    <div class="slot slot--18-4 slot--left">
        <div class="team team--home">
            <span class="team__name">Węgry</span>
            <span class="team__score">0</span>
        </div>
        <div class="team team--away team--winner">
            <span class="team__name">Belgia</span>
            <span class="team__score">4</span>
        </div>
    </div>
    <div class="slot slot--18-5 slot--top slot--right">
        <div class="team team--home team--winner">
            <span class="team__name">Niemcy</span>
            <span class="team__score">3</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Słowacja</span>
            <span class="team__score">0</span>
        </div>
    </div>
    <div class="slot slot--18-6 slot--right">
        <div class="team team--home team--winner">
            <span class="team__name">Włochy</span>
            <span class="team__score">2</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Hiszpania</span>
            <span class="team__score">0</span>
        </div>
    </div>
    <div class="slot slot--18-7 slot--top slot--right">
        <div class="team team--home team--winner">
            <span class="team__name">Francja</span>
            <span class="team__score">2</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Irlandia</span>
            <span class="team__score">1</span>
        </div>
    </div>
    <div class="slot slot--18-8 slot--right">
        <div class="team team--home">
            <span class="team__name">Anglia</span>
            <span class="team__score">1</span>
        </div>
        <div class="team team--away team--winner">
            <span class="team__name">Islandia</span>
            <span class="team__score">2</span>
        </div>
    </div>
    <div class="slot slot--14-1 slot--left">
        <div class="team team--home">
            <span class="team__name">Polska</span>
            <span class="team__score">1</span>
        </div>
        <div class="team team--away team--winner">
            <span class="team__name">Portugalia</span>
            <span class="team__score">1</span>
        </div>
    </div>
    <div class="slot slot--14-2 slot--left">
        <div class="team team--home team--winner">
            <span class="team__name">Walia</span>
            <span class="team__score">3</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Belgia</span>
            <span class="team__score">1</span>
        </div>
    </div>
    <div class="slot slot--14-3 slot--right">
        <div class="team team--home team--winner">
            <span class="team__name">Niemcy</span>
            <span class="team__score">1</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Włochy</span>
            <span class="team__score">1</span>
        </div>
    </div>
    <div class="slot slot--14-4 slot--right">
        <div class="team team--home team--winner">
            <span class="team__name">Francja</span>
            <span class="team__score">5</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Islandia</span>
            <span class="team__score">2</span>
        </div>
    </div>
    <div class="slot slot--12-1 slot--left">
        <div class="team team--home team--winner">
            <span class="team__name">Portugalia</span>
            <span class="team__score">2</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Walia</span>
            <span class="team__score">0</span>
        </div>
    </div>
    <div class="slot slot--12-2 slot--right">
        <div class="team team--home">
            <span class="team__name">Niemcy</span>
            <span class="team__score">0</span>
        </div>
        <div class="team team--away team--winner">
            <span class="team__name">Francja</span>
            <span class="team__score">2</span>
        </div>
    </div>
    <div class="slot slot--11-1">
        <div class="team team--home team--winner">
            <span class="team__name">Portugalia</span>
            <span class="team__score">1</span>
        </div>
        <div class="team team--away">
            <span class="team__name">Francja</span>
            <span class="team__score">0</span>
        </div>
    </div>
</div>

Mamy kontener z klasą .tournamen-tree wraz kontenerami meczów wewnątrz niego i nic poza tym! To jest zaleta CSS Grid. Nie potrzebujemy żadnego dodatkowego kodu HTML, aby poukładać elementy i osiągnąć wymagany efekt.

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
.tournament-tree {
    display: grid;
    width: 1216px;
    margin: auto;
    grid-gap: 16px;
    grid-template-rows: 80px;
    grid-template-columns: 160px;
    grid-template-areas:
        'slot--18-1 . . . . . slot--18-5'
        '. slot--14-1 . . . slot--14-3 .'
        'slot--18-2 . . . . . slot--18-6'
        '. . slot--12-1 slot--11-1 slot--12-2 . .'
        'slot--18-3 . . . . . slot--18-7'
        '. slot--14-2 . . . slot--14-4 .'
        'slot--18-4 . . . . . slot--18-8';
}
 
.slot--18-1 { grid-area: slot--18-1; }
.slot--18-2 { grid-area: slot--18-2; }
.slot--18-3 { grid-area: slot--18-3; }
.slot--18-4 { grid-area: slot--18-4; }
.slot--18-5 { grid-area: slot--18-5; }
.slot--18-6 { grid-area: slot--18-6; }
.slot--18-7 { grid-area: slot--18-7; }
.slot--18-8 { grid-area: slot--18-8; }
 
.slot--14-1 { grid-area: slot--14-1; }
.slot--14-2 { grid-area: slot--14-2; }
.slot--14-3 { grid-area: slot--14-3; }
.slot--14-4 { grid-area: slot--14-4; }
 
.slot--12-1 { grid-area: slot--12-1; }
.slot--12-2 { grid-area: slot--12-2; }
 
.slot--11-1 { grid-area: slot--11-1; }
 
.slot {
    border: 1px solid #263e6d;
    background: #fff;
    position: relative;
    height: 80px;
    width: 160px;
}
 
.slot:after,
.slot:before {
    content: '';
    background: #b70711;
    position: absolute;
    top: 50%;
}
 
/** right side **/
.slot--left:after,
.slot--11-1:after,
[class*="slot--14-"].slot--right:before {
    width: 8px;
    height: 2px;
    right: 0;
    transform: translate(100%, -50%);
}
 
/** left side **/
.slot--right:after,
.slot--11-1:before,
[class*="slot--14-"].slot--left:before {
    width: 8px;
    height: 2px;
    left: 0;
    transform: translate(-100%, -50%);
}
 
[class*="slot--12-"].slot--left:after {
    left: 0;
    transform: translate(-100%, -50%);
}
 
[class*="slot--12-"].slot--left:before,
[class*="slot--12-"].slot--right:before {
    width: 2px;
    height: 386px;
}
 
.slot--12-1:before {
    left: 0;
    transform: translate(-10px, -50%);
}
 
[class*="slot--12-"].slot--right:after {
    left: auto;
    right: 0;
    transform: translate(100%, -50%);
}
 
[class*="slot--12-"].slot--right:before {
    right: 0;
    left: auto;
    transform: translate(10px, -50%);
}
 
[class*="slot--18-"].slot--top:before {
    width: 2px;
    height: 194px;
}
 
[class*="slot--18-"].slot--top.slot--left:before {
    right: 0;
    transform: translate(10px, -1px);
}
 
[class*="slot--18-"].slot--top.slot--right:before {
    left: 0;
    transform: translate(-10px, -1px);
}
 
.slot--11-1:after,
.slot--11-1:before {
    width: 18px;
}
 
.team {
    height: 40px;
    display: flex;
    justify-content: space-between;
    align-items: stretch;
    font-weight: 700;
    background: #ddd;
}
 
.team--home {
    border-bottom: 2px solid #b70711;
    color: #b70711;
}
 
.team--away {
    color: #263e6d;
    height: 38px;
}
 
.team--winner {
    background: #fff;
}
 
.team__name,
.team__score {
    padding: 8px;
    display: flex;
    align-items: center;
}
 
.team__score {
    background: #263e6d;
    color: #d0d3db;
    flex: 0 0 30px;
    justify-content: center;
}

Powyższy kod CSS może się wydawać skomplikowany, ale tak naprawdę nie jest. Dla każdego z meczów w danej fazie pucharu określiłem odpowiedni obszar gdzie ma się pojawić dany mecz. Następnie, pomiędzy fazami pucharowymi są narysowane linie, które pokazują ścieżkę pucharową. Linie są pseudoelementami, czyli istnieją tylko i wyłącznie w CSS. Linie zostały określone za pomocą selektorów: :before i :after.

Podsumowanie

Mam nadzieję, że udało mi się wyjaśnić problematykę zagadnienia CSS Grid Areas. Jest to bardzo użyteczne narzędzie do układania elementów na stronie za pomocą CSS. Dzięki niemu, mamy kontrolę na pozycjami elementów. W chwili obecnej, nie da się zrobić tak, aby w zaplanowanej przez nas siatce umieścić kilka komórek o tej samej nazwie i sprawić, aby elementy przechodziły między nimi gdy skończy się dostępna przestrzeń. Póki co, ten temat jest w trakcie prac w organizacji W3C.

Końcowy rezultat prac można zobaczyć tutaj:

Zobacz na CodePen EURO2016 - Tournament tree with CSS Grid by sunpietro (@sunpietro).

Zapraszam do komentowania i zadawania pytań w przypadku niejasności.