Kurs HTML i CSS · Moduł 5: Responsywność

Projekt główny - Responsywna Galeria Portfolio Fotografa

11 min czytania
W tej lekcji7

Znajomy fotograf potrzebuje strony, która pokaże jego zdjęcia równie dobrze na telefonie klienta, jak na monitorze w biurze. W tym projekcie stworzymy profesjonalną, w pełni responsywną galerię portfolio fotografa. Wykorzystamy wszystkie techniki z tego modułu: media queries, jednostki responsywne (vw, vh), viewport, optymalizację obrazów oraz podejście mobile-first. Galeria będzie zawierać nawigację, sekcję hero, siatkę zdjęć z efektami hover, filtrowanie kategorii oraz lightbox do przeglądania zdjęć.

Opis projektu

Nasza galeria portfolio będzie miała następujące funkcjonalności:

  • Responsywna nawigacja - hamburger menu na urządzeniach mobilnych
  • Sekcja Hero - pełnoekranowa z wykorzystaniem jednostek viewport
  • Galeria zdjęć - responsywna siatka z efektami hover
  • Filtrowanie kategorii - przyciski filtrowania z interaktywnym designem
  • Lightbox - przeglądanie zdjęć w powiększeniu
  • Optymalizacja obrazów - różne rozmiary dla różnych urządzeń

Krok 1: Struktura HTML z podejściem Mobile-First

Zaczynamy od struktury HTML pisanej z myślą o telefonach. Zwróć uwagę na znacznik viewport w head i na elementy picture w galerii:

1<!DOCTYPE html>
2<html lang="pl">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Jan Kowalski - Portfolio Fotografa</title>
7    <link rel="stylesheet" href="styles.css">
8</head>
9<body>
10    <!-- Nawigacja -->
11    <nav class="navbar">
12        <div class="nav-container">
13            <div class="nav-logo">JK Photography</div>
14            <button class="nav-toggle" aria-label="Menu">
15                <span></span>
16                <span></span>
17                <span></span>
18            </button>
19            <ul class="nav-menu">
20                <li><a href="#home">Start</a></li>
21                <li><a href="#portfolio">Portfolio</a></li>
22                <li><a href="#about">O mnie</a></li>
23                <li><a href="#contact">Kontakt</a></li>
24            </ul>
25        </div>
26    </nav>
27
28    <!-- Sekcja Hero -->
29    <section id="home" class="hero">
30        <div class="hero-content">
31            <h1 class="hero-title">Jan Kowalski</h1>
32            <p class="hero-subtitle">Fotografia jest moją pasją</p>
33            <a href="#portfolio" class="hero-button">Zobacz portfolio</a>
34        </div>
35        <div class="hero-overlay"></div>
36    </section>
37
38    <!-- Sekcja Portfolio -->
39    <section id="portfolio" class="portfolio">
40        <div class="container">
41            <h2 class="section-title">Moje Portfolio</h2>
42
43            <!-- Filtry kategorii -->
44            <div class="filter-buttons">
45                <button class="filter-btn active" data-filter="all">Wszystkie</button>
46                <button class="filter-btn" data-filter="nature">Natura</button>
47                <button class="filter-btn" data-filter="portrait">Portrety</button>
48                <button class="filter-btn" data-filter="architecture">Architektura</button>
49            </div>
50
51            <!-- Galeria -->
52            <div class="gallery">
53                <div class="gallery-item" data-category="nature">
54                    <picture>
55                        <source media="(max-width: 768px)" srcset="img/nature1-mobile.jpg">
56                        <source media="(max-width: 1200px)" srcset="img/nature1-tablet.jpg">
57                        <img src="img/nature1-desktop.jpg" alt="Krajobraz górski">
58                    </picture>
59                    <div class="gallery-overlay">
60                        <h3>Krajobraz górski</h3>
61                        <p>Natura</p>
62                    </div>
63                </div>
64
65                <div class="gallery-item" data-category="portrait">
66                    <picture>
67                        <source media="(max-width: 768px)" srcset="img/portrait1-mobile.jpg">
68                        <source media="(max-width: 1200px)" srcset="img/portrait1-tablet.jpg">
69                        <img src="img/portrait1-desktop.jpg" alt="Portret kobiety">
70                    </picture>
71                    <div class="gallery-overlay">
72                        <h3>Portret</h3>
73                        <p>Portrety</p>
74                    </div>
75                </div>
76
77                <!-- Więcej elementów galerii... -->
78            </div>
79        </div>
80    </section>
81
82    <!-- Lightbox -->
83    <div class="lightbox" id="lightbox">
84        <span class="lightbox-close">&times;</span>
85        <img class="lightbox-content" id="lightbox-img">
86        <div class="lightbox-caption"></div>
87    </div>
88</body>
89</html>

Przycisk nav-toggle to hamburger, a jego aria-label mówi czytnikom ekranu, do czego służy. Atrybuty data-category przygotowują filtrowanie. Hamburger, filtry i lightbox ożyją dopiero ze skryptem, który napiszesz po poznaniu JavaScriptu, a CSS przygotuje dla nich stany z klasą active.

Krok 2: CSS Mobile-First z jednostkami responsywnymi

Stylowanie zaczynamy od telefonów i dzielimy arkusz na cztery etapy. Pierwszy to reset, zmienne CSS zapisane raz w :root i wstawiane przez var(), oraz nawigacja z menu schowanym pod hamburgerem:

1/* Reset i podstawowe style */
2* {
3    margin: 0;
4    padding: 0;
5    box-sizing: border-box;
6}
7
8:root {
9    --primary-color: #333;
10    --secondary-color: #f4f4f4;
11    --accent-color: #ff6b6b;
12    --text-light: #fff;
13    --overlay-dark: rgba(0, 0, 0, 0.7);
14}
15
16body {
17    font-family: 'Arial', sans-serif;
18    line-height: 1.6;
19    color: var(--primary-color);
20}
21
22/* Nawigacja - Mobile First */
23.navbar {
24    background-color: var(--primary-color);
25    padding: 1rem;
26    position: fixed;
27    width: 100%;
28    top: 0;
29    z-index: 1000;
30}
31
32.nav-container {
33    display: flex;
34    justify-content: space-between;
35    align-items: center;
36}
37
38.nav-logo {
39    color: var(--text-light);
40    font-size: 1.5rem;
41    font-weight: bold;
42}
43
44/* Hamburger menu dla mobile */
45.nav-toggle {
46    background: none;
47    border: none;
48    cursor: pointer;
49    display: flex;
50    flex-direction: column;
51    gap: 4px;
52}
53
54.nav-toggle span {
55    width: 25px;
56    height: 3px;
57    background-color: var(--text-light);
58    transition: all 0.3s ease;
59}
60
61.nav-menu {
62    position: absolute;
63    top: 100%;
64    left: 0;
65    width: 100%;
66    background-color: var(--primary-color);
67    list-style: none;
68    display: none;
69    flex-direction: column;
70}
71
72.nav-menu.active {
73    display: flex;
74}
75
76.nav-menu li {
77    padding: 1rem;
78    text-align: center;
79}
80
81.nav-menu a {
82    color: var(--text-light);
83    text-decoration: none;
84    transition: color 0.3s ease;
85}

box-sizing: border-box sprawia, że wypełnienie nie poszerza elementów. Pasek z position: fixed zostaje na górze podczas przewijania, a menu z display: none czeka, aż skrypt doda mu klasę active. Drugi etap to sekcja hero, portfolio i filtry:

1/* Hero section z jednostkami viewport */
2.hero {
3    height: 100vh;
4    width: 100vw;
5    position: relative;
6    display: flex;
7    align-items: center;
8    justify-content: center;
9    background-image: url('img/hero-mobile.jpg');
10    background-size: cover;
11    background-position: center;
12}
13
14.hero-overlay {
15    position: absolute;
16    top: 0;
17    left: 0;
18    width: 100%;
19    height: 100%;
20    background-color: var(--overlay-dark);
21}
22
23.hero-content {
24    position: relative;
25    z-index: 1;
26    text-align: center;
27    color: var(--text-light);
28    padding: 0 1rem;
29}
30
31.hero-title {
32    font-size: clamp(2rem, 8vw, 4rem);
33    margin-bottom: 1rem;
34}
35
36.hero-subtitle {
37    font-size: clamp(1rem, 4vw, 1.5rem);
38    margin-bottom: 2rem;
39}
40
41.hero-button {
42    display: inline-block;
43    padding: 0.8rem 2rem;
44    background-color: var(--accent-color);
45    color: var(--text-light);
46    text-decoration: none;
47    border-radius: 5px;
48    transition: all 0.3s ease;
49}
50
51/* Portfolio section */
52.portfolio {
53    padding: 4rem 1rem;
54    background-color: var(--secondary-color);
55}
56
57.container {
58    max-width: 1200px;
59    margin: 0 auto;
60}
61
62.section-title {
63    text-align: center;
64    font-size: clamp(1.5rem, 5vw, 2.5rem);
65    margin-bottom: 2rem;
66}
67
68/* Filtry */
69.filter-buttons {
70    display: flex;
71    flex-wrap: wrap;
72    justify-content: center;
73    gap: 0.5rem;
74    margin-bottom: 2rem;
75}
76
77.filter-btn {
78    padding: 0.5rem 1rem;
79    border: 2px solid var(--primary-color);
80    background-color: transparent;
81    cursor: pointer;
82    transition: all 0.3s ease;
83    border-radius: 25px;
84}
85
86.filter-btn.active,
87.filter-btn:hover {
88    background-color: var(--primary-color);
89    color: var(--text-light);
90}

height: 100vh to cała wysokość okna. clamp(2rem, 8vw, 4rem) przyjmuje minimum, wartość preferowaną i maksimum, więc tytuł rośnie z ekranem, ale nie spada poniżej 2rem ani nie przekracza 4rem. Kontener z max-width: 1200px trzyma treść pośrodku. Trzeci etap to galeria i lightbox:

1/* Galeria - Mobile First Grid */
2.gallery {
3    display: grid;
4    grid-template-columns: 1fr;
5    gap: 1rem;
6}
7
8.gallery-item {
9    position: relative;
10    overflow: hidden;
11    cursor: pointer;
12    aspect-ratio: 1;
13}
14
15.gallery-item img {
16    width: 100%;
17    height: 100%;
18    object-fit: cover;
19    transition: transform 0.3s ease;
20}
21
22.gallery-overlay {
23    position: absolute;
24    bottom: 0;
25    left: 0;
26    right: 0;
27    background: linear-gradient(to top, var(--overlay-dark), transparent);
28    color: var(--text-light);
29    padding: 1rem;
30    transform: translateY(100%);
31    transition: transform 0.3s ease;
32}
33
34.gallery-item:hover .gallery-overlay {
35    transform: translateY(0);
36}
37
38.gallery-item:hover img {
39    transform: scale(1.1);
40}
41
42/* Lightbox */
43.lightbox {
44    display: none;
45    position: fixed;
46    z-index: 2000;
47    left: 0;
48    top: 0;
49    width: 100vw;
50    height: 100vh;
51    background-color: var(--overlay-dark);
52    align-items: center;
53    justify-content: center;
54}
55
56.lightbox.active {
57    display: flex;
58}
59
60.lightbox-content {
61    max-width: 90vw;
62    max-height: 90vh;
63    object-fit: contain;
64}
65
66.lightbox-close {
67    position: absolute;
68    top: 1rem;
69    right: 1rem;
70    color: var(--text-light);
71    font-size: 2rem;
72    cursor: pointer;
73}

Na telefonie galeria ma jedną kolumnę 1fr. aspect-ratio: 1 robi z kafelka kwadrat, a object-fit: cover przycina zdjęcie bez zniekształceń. Lightbox z klasą active staje się kontenerem flex, a max-width: 90vw pilnuje, by zdjęcie mieściło się w oknie. Czwarty etap to progi dla większych ekranów:

1/* Media Queries - Tablet */
2@media (min-width: 768px) {
3    /* Nawigacja dla tabletu */
4    .nav-toggle {
5        display: none;
6    }
7
8    .nav-menu {
9        position: static;
10        display: flex;
11        flex-direction: row;
12        width: auto;
13    }
14
15    .nav-menu li {
16        padding: 0 1rem;
17    }
18
19    /* Hero dla tabletu */
20    .hero {
21        background-image: url('img/hero-tablet.jpg');
22    }
23
24    /* Galeria - 2 kolumny */
25    .gallery {
26        grid-template-columns: repeat(2, 1fr);
27    }
28}
29
30/* Media Queries - Desktop */
31@media (min-width: 1024px) {
32    /* Hero dla desktop */
33    .hero {
34        background-image: url('img/hero-desktop.jpg');
35    }
36
37    /* Galeria - 3 kolumny */
38    .gallery {
39        grid-template-columns: repeat(3, 1fr);
40        gap: 1.5rem;
41    }
42
43    /* Efekty hover tylko na desktop */
44    .gallery-overlay {
45        opacity: 0;
46        transform: translateY(0);
47        background: var(--overlay-dark);
48    }
49
50    .gallery-item:hover .gallery-overlay {
51        opacity: 1;
52    }
53}
54
55/* Media Queries - Duże ekrany */
56@media (min-width: 1440px) {
57    .gallery {
58        grid-template-columns: repeat(4, 1fr);
59    }
60}
61
62/* Orientacja landscape dla mobile */
63@media (max-width: 768px) and (orientation: landscape) {
64    .hero {
65        height: 100vw;
66    }
67
68    .hero-title {
69        font-size: 2rem;
70    }
71}
72
73/* High DPI displays */
74@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
75    .hero {
76        background-image: url('img/hero-mobile@2x.jpg');
77    }
78
79    @media (min-width: 768px) {
80        .hero {
81            background-image: url('img/hero-tablet@2x.jpg');
82        }
83    }
84
85    @media (min-width: 1024px) {
86        .hero {
87            background-image: url('img/hero-desktop@2x.jpg');
88        }
89    }
90}

Od 768 pikseli znika hamburger, menu staje w rzędzie, a galeria dostaje dwie kolumny, od 1024 trzy, a od 1440 cztery. Warunek orientation: landscape łapie obrócony telefon, a min-resolution: 192dpi ekrany o podwójnej gęstości pikseli, którym podajemy zdjęcia w wyższej rozdzielczości.

Krok 3: Optymalizacja i najlepsze praktyki

Optymalizacja obrazów:

Element picture pozwala przeglądarce wybrać plik: sprawdza warunek media i to, czy obsługuje format podany w type:

1<!-- Użyj elementu picture dla responsywnych obrazów -->
2<picture>
3    <source media="(max-width: 768px)"
4            srcset="img/photo-mobile.webp"
5            type="image/webp">
6    <source media="(max-width: 768px)"
7            srcset="img/photo-mobile.jpg"
8            type="image/jpeg">
9    <source media="(max-width: 1200px)"
10            srcset="img/photo-tablet.webp"
11            type="image/webp">
12    <source media="(max-width: 1200px)"
13            srcset="img/photo-tablet.jpg"
14            type="image/jpeg">
15    <source srcset="img/photo-desktop.webp"
16            type="image/webp">
17    <img src="img/photo-desktop.jpg"
18         alt="Opis zdjęcia"
19         loading="lazy">
20</picture>

Nowoczesne przeglądarki pobiorą lekki WebP, starsze sięgną po JPEG, a loading="lazy" opóźni pobieranie zdjęć spoza ekranu.

Dodatkowe style dla wydajności:

Na koniec kilka reguł dla wydajności, dostępności i trybu ciemnego:

1/* Użyj will-change dla elementów animowanych */
2.gallery-item {
3    will-change: transform;
4}
5
6/* Optymalizacja dla wydajności */
7.gallery-item img {
8    will-change: transform;
9    backface-visibility: hidden;
10}
11
12/* Użyj CSS containment */
13.gallery-item {
14    contain: layout style;
15}
16
17/* Prefers reduced motion */
18@media (prefers-reduced-motion: reduce) {
19    * {
20        animation-duration: 0.01ms !important;
21        animation-iteration-count: 1 !important;
22        transition-duration: 0.01ms !important;
23    }
24}
25
26/* Dark mode support */
27@media (prefers-color-scheme: dark) {
28    :root {
29        --primary-color: #fff;
30        --secondary-color: #1a1a1a;
31        --text-light: #333;
32    }
33
34    body {
35        background-color: #1a1a1a;
36        color: #fff;
37    }
38}

contain ogranicza przeliczenia do jednego kafelka, prefers-reduced-motion skraca animacje osobom, które wyłączyły ruch w systemie, a prefers-color-scheme: dark włącza ciemne kolory.

Krok 4: Siatka Grid bez media queries

Galeria korzysta z CSS Grid, który poznasz dokładnie w lokacji mu poświęconej. grid-template-columns: 1fr 1fr 1fr tworzy trzy równe kolumny, bo fr to ułamek wolnego miejsca, a repeat(3, 1fr) to ten sam zapis w skrócie. Grid potrafi też sam dobrać liczbę kolumn:

1.gallery {
2  display: grid;
3  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
4  gap: 1rem;
5}

minmax(250px, 1fr) daje kolumnie co najmniej 250 pikseli, a auto-fit mieści ich tyle, ile się zmieści, więc media queries nie są potrzebne. Grid układa elementy w dwóch wymiarach naraz, a Flexbox w jednym, dlatego szkielet strony to zadanie dla Grida i jego nazwanych obszarów:

1.page {
2  display: grid;
3  grid-template-columns: 200px 1fr 200px;
4  grid-template-areas:
5    "header header header"
6    "nav main aside"
7    "footer footer footer";
8}
9
10header { grid-area: header; }
11nav { grid-area: nav; }
12main { grid-area: main; }
13aside { grid-area: aside; }
14footer { grid-area: footer; }

grid-template-areas rysuje plan strony z nazw, jak plan świątyni z salą główną i bocznymi komnatami, a grid-area przypisuje element do obszaru. Na telefonie wystarczy jedna kolumna i obszary jeden pod drugim. Szerokości policzysz też funkcją calc(), na przykład calc(100% - 20px). Tę samą galerię zbudujesz na Flexboksie, zmieniając w media queries flex-basis kafelków ze 100% na calc(50% - 10px) i calc(33.33% - 14px).

Kompletny przykład kodu

HTML (index.html):

W porównaniu z krokiem 1 pełny plik ma opis strony w meta, połączenia preconnect, przykładowe zdjęcia z picsum.photos i skrypt script.js:

1<!DOCTYPE html>
2<html lang="pl">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <meta name="description" content="Portfolio fotograficzne Jana Kowalskiego">
7    <title>Jan Kowalski - Portfolio Fotografa</title>
8    <link rel="stylesheet" href="styles.css">
9    <link rel="preconnect" href="https://fonts.googleapis.com">
10    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11</head>
12<body>
13    <!-- Nawigacja -->
14    <nav class="navbar">
15        <div class="nav-container">
16            <div class="nav-logo">JK Photography</div>
17            <button class="nav-toggle" aria-label="Menu">
18                <span></span>
19                <span></span>
20                <span></span>
21            </button>
22            <ul class="nav-menu">
23                <li><a href="#home">Start</a></li>
24                <li><a href="#portfolio">Portfolio</a></li>
25                <li><a href="#about">O mnie</a></li>
26                <li><a href="#contact">Kontakt</a></li>
27            </ul>
28        </div>
29    </nav>
30
31    <!-- Sekcja Hero -->
32    <section id="home" class="hero">
33        <div class="hero-content">
34            <h1 class="hero-title">Jan Kowalski</h1>
35            <p class="hero-subtitle">Fotografia jest moją pasją</p>
36            <a href="#portfolio" class="hero-button">Zobacz portfolio</a>
37        </div>
38        <div class="hero-overlay"></div>
39    </section>
40
41    <!-- Sekcja Portfolio -->
42    <section id="portfolio" class="portfolio">
43        <div class="container">
44            <h2 class="section-title">Moje Portfolio</h2>
45
46            <!-- Filtry kategorii -->
47            <div class="filter-buttons">
48                <button class="filter-btn active" data-filter="all">Wszystkie</button>
49                <button class="filter-btn" data-filter="nature">Natura</button>
50                <button class="filter-btn" data-filter="portrait">Portrety</button>
51                <button class="filter-btn" data-filter="architecture">Architektura</button>
52            </div>
53
54            <!-- Galeria -->
55            <div class="gallery">
56                <!-- Przykładowe elementy galerii -->
57                <div class="gallery-item" data-category="nature">
58                    <picture>
59                        <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=1">
60                        <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=1">
61                        <img src="https://picsum.photos/800/800?random=1" alt="Krajobraz górski">
62                    </picture>
63                    <div class="gallery-overlay">
64                        <h3>Krajobraz górski</h3>
65                        <p>Natura</p>
66                    </div>
67                </div>
68
69                <div class="gallery-item" data-category="portrait">
70                    <picture>
71                        <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=2">
72                        <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=2">
73                        <img src="https://picsum.photos/800/800?random=2" alt="Portret">
74                    </picture>
75                    <div class="gallery-overlay">
76                        <h3>Portret</h3>
77                        <p>Portrety</p>
78                    </div>
79                </div>
80
81                <div class="gallery-item" data-category="architecture">
82                    <picture>
83                        <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=3">
84                        <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=3">
85                        <img src="https://picsum.photos/800/800?random=3" alt="Architektura">
86                    </picture>
87                    <div class="gallery-overlay">
88                        <h3>Nowoczesna architektura</h3>
89                        <p>Architektura</p>
90                    </div>
91                </div>
92
93                <div class="gallery-item" data-category="nature">
94                    <picture>
95                        <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=4">
96                        <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=4">
97                        <img src="https://picsum.photos/800/800?random=4" alt="Las">
98                    </picture>
99                    <div class="gallery-overlay">
100                        <h3>Leśna ścieżka</h3>
101                        <p>Natura</p>
102                    </div>
103                </div>
104
105                <div class="gallery-item" data-category="portrait">
106                    <picture>
107                        <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=5">
108                        <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=5">
109                        <img src="https://picsum.photos/800/800?random=5" alt="Portret dziecka">
110                    </picture>
111                    <div class="gallery-overlay">
112                        <h3>Dziecięca radość</h3>
113                        <p>Portrety</p>
114                    </div>
115                </div>
116
117                <div class="gallery-item" data-category="architecture">
118                    <picture>
119                        <source media="(max-width: 768px)" srcset="https://picsum.photos/400/400?random=6">
120                        <source media="(max-width: 1200px)" srcset="https://picsum.photos/600/600?random=6">
121                        <img src="https://picsum.photos/800/800?random=6" alt="Most">
122                    </picture>
123                    <div class="gallery-overlay">
124                        <h3>Most o zachodzie</h3>
125                        <p>Architektura</p>
126                    </div>
127                </div>
128            </div>
129        </div>
130    </section>
131
132    <!-- Lightbox -->
133    <div class="lightbox" id="lightbox">
134        <span class="lightbox-close">&times;</span>
135        <img class="lightbox-content" id="lightbox-img">
136        <div class="lightbox-caption"></div>
137    </div>
138
139    <script src="script.js"></script>
140</body>
141</html>

Podsumowanie

Ten projekt demonstruje wszystkie kluczowe koncepty responsywnego projektowania:

  1. Mobile-First Approach - rozpoczynamy od stylów mobilnych
  2. Media Queries - dostosowujemy layout dla różnych urządzeń
  3. Jednostki Viewport (vw, vh) - dla pełnoekranowych sekcji
  4. Responsywne obrazy - element picture z różnymi źródłami
  5. CSS Grid - responsywna siatka galerii
  6. Flexbox - elastyczna nawigacja
  7. Clamp() - responsywne rozmiary czcionek
  8. Optymalizacja - lazy loading, will-change, contain
  9. Dostępność - semantyczny HTML, aria-labels
  10. Nowoczesne funkcje - dark mode, reduced motion

Warstwa HTML i CSS jest gotowa i łatwo ją dostosować do innych celów, a menu, filtry i lightbox potrzebują jeszcze krótkiego skryptu. Moja rada: zanim wyślesz projekt mentorowi, sprawdź go na szerokościach 320, 768, 1024 i 1440 pikseli.

Pamiętaj: responsywna galeria to dziedziniec świątyni, który mieści i garstkę kapłanów, i tłum pielgrzymów, bo jego układ rośnie razem z ekranem.

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. Jaka wartość grid-template-columns tworzy responsywną siatkę?

  2. 2. Jak utworzyć siatkę CSS Grid z trzema równymi kolumnami?

To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Klikanie w kolejności

    Ułóż kod CSS tworzący siatkę Grid z 3 kolumnami:

  • Edytor kodu

    Stwórz responsywną siatkę egipskich bogów z Flexbox. HTML: <div class='pantheon'> + 6 <div class='god'>Bóg X</div> + </div>. CSS bazowy (mobile): .pantheon {display: flex; flex-wrap: wrap; gap: 20px; padding: 20px;} .god {flex-basis: 100%; background: #FFD700; padding: 30px; text-align: center; border: 2px solid #8B4513;}. Tablet: @media (min-width: 768px) {.god {flex-basis: calc(50% - 10px);}}. Desktop: @media (min-width: 1024px) {.god {flex-basis: calc(33.33% - 14px);}}. Obserwuj jak elementy się reorganizują!

  • Układanie w pionie

    Ułóż deklarację CSS Grid z automatycznym dopasowaniem kolumn (od aktywacji do wartości minmax):

  • Edytor kodu

    Stwórz auto-responsywną siatkę używając repeat(auto-fit, minmax(250px, 1fr)).

  • Edytor kodu

    Uzupełnij styl CSS: dodaj właściwości grid-template-columns i grid-template-areas dla widoku mobilnego (1 kolumna), a następnie w media query (min-width: 768px) ustaw layout 2-kolumnowy z sidebar i main obok siebie.

  • Układanie w poziomie

    Ułóż elementy aby stworzyć poprawne użycie calc():

  • Edytor kodu

    Stwórz Holy Grail Layout używając CSS Grid. HTML: <div class='temple-layout'><header class='header'>Nagłówek</header><nav class='sidebar-left'>Lewy</nav><main class='main-content'>Treść</main><aside class='sidebar-right'>Prawy</aside><footer class='footer'>Stopka</footer></div>. CSS: .temple-layout {display: grid; grid-template-areas: 'header header header' 'sidebar-left main-content sidebar-right' 'footer footer footer'; grid-template-columns: 200px 1fr 200px; grid-template-rows: auto 1fr auto; min-height: 100vh; gap: 10px;} .header {grid-area: header; background: #FFD700; padding: 20px;} .sidebar-left {grid-area: sidebar-left; background: #F4E4C1; padding: 20px;} .main-content {grid-area: main-content; background: #FFF8DC; padding: 20px;} .sidebar-right {grid-area: sidebar-right; background: #F4E4C1; padding: 20px;} .footer {grid-area: footer; background: #FFD700; padding: 20px;}.

  • Edytor kodu

    Stwórz responsywną siatkę kart bogów egipskich. HTML: <div class='cards-grid'><div class='card'><h2>Ra</h2><p>Bóg Słońca</p></div><div class='card'><h2>Anubis</h2><p>Bóg Zmarłych</p></div><div class='card'><h2>Isis</h2><p>Bogini Magii</p></div><div class='card'><h2>Horus</h2><p>Bóg Nieba</p></div><div class='card'><h2>Thoth</h2><p>Bóg Wiedzy</p></div><div class='card'><h2>Bastet</h2><p>Bogini Kotów</p></div></div>. CSS: .cards-grid {display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; padding: 20px;} .card {background: #FFD700; padding: 30px; text-align: center; border: 2px solid #8B4513; border-radius: 10px;}.

Przydatne artykuły