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:
- 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).
- 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:
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:
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:
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:
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.
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.