Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Projekt główny - Responsywna Galeria Portfolio Fotografa

W tym projekcie stworzymy profesjonalną, w pełni responsywną galerię portfolio dla 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 stworzenia struktury HTML z myślą o urządzeniach mobilnych:

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>

Krok 2: CSS Mobile-First z jednostkami responsywnymi

Rozpoczynamy stylowanie od urządzeń mobilnych:

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}
86
87/* Hero section z jednostkami viewport */
88.hero {
89    height: 100vh;
90    width: 100vw;
91    position: relative;
92    display: flex;
93    align-items: center;
94    justify-content: center;
95    background-image: url('img/hero-mobile.jpg');
96    background-size: cover;
97    background-position: center;
98}
99
100.hero-overlay {
101    position: absolute;
102    top: 0;
103    left: 0;
104    width: 100%;
105    height: 100%;
106    background-color: var(--overlay-dark);
107}
108
109.hero-content {
110    position: relative;
111    z-index: 1;
112    text-align: center;
113    color: var(--text-light);
114    padding: 0 1rem;
115}
116
117.hero-title {
118    font-size: clamp(2rem, 8vw, 4rem);
119    margin-bottom: 1rem;
120}
121
122.hero-subtitle {
123    font-size: clamp(1rem, 4vw, 1.5rem);
124    margin-bottom: 2rem;
125}
126
127.hero-button {
128    display: inline-block;
129    padding: 0.8rem 2rem;
130    background-color: var(--accent-color);
131    color: var(--text-light);
132    text-decoration: none;
133    border-radius: 5px;
134    transition: all 0.3s ease;
135}
136
137/* Portfolio section */
138.portfolio {
139    padding: 4rem 1rem;
140    background-color: var(--secondary-color);
141}
142
143.container {
144    max-width: 1200px;
145    margin: 0 auto;
146}
147
148.section-title {
149    text-align: center;
150    font-size: clamp(1.5rem, 5vw, 2.5rem);
151    margin-bottom: 2rem;
152}
153
154/* Filtry */
155.filter-buttons {
156    display: flex;
157    flex-wrap: wrap;
158    justify-content: center;
159    gap: 0.5rem;
160    margin-bottom: 2rem;
161}
162
163.filter-btn {
164    padding: 0.5rem 1rem;
165    border: 2px solid var(--primary-color);
166    background-color: transparent;
167    cursor: pointer;
168    transition: all 0.3s ease;
169    border-radius: 25px;
170}
171
172.filter-btn.active,
173.filter-btn:hover {
174    background-color: var(--primary-color);
175    color: var(--text-light);
176}
177
178/* Galeria - Mobile First Grid */
179.gallery {
180    display: grid;
181    grid-template-columns: 1fr;
182    gap: 1rem;
183}
184
185.gallery-item {
186    position: relative;
187    overflow: hidden;
188    cursor: pointer;
189    aspect-ratio: 1;
190}
191
192.gallery-item img {
193    width: 100%;
194    height: 100%;
195    object-fit: cover;
196    transition: transform 0.3s ease;
197}
198
199.gallery-overlay {
200    position: absolute;
201    bottom: 0;
202    left: 0;
203    right: 0;
204    background: linear-gradient(to top, var(--overlay-dark), transparent);
205    color: var(--text-light);
206    padding: 1rem;
207    transform: translateY(100%);
208    transition: transform 0.3s ease;
209}
210
211.gallery-item:hover .gallery-overlay {
212    transform: translateY(0);
213}
214
215.gallery-item:hover img {
216    transform: scale(1.1);
217}
218
219/* Lightbox */
220.lightbox {
221    display: none;
222    position: fixed;
223    z-index: 2000;
224    left: 0;
225    top: 0;
226    width: 100vw;
227    height: 100vh;
228    background-color: var(--overlay-dark);
229    align-items: center;
230    justify-content: center;
231}
232
233.lightbox.active {
234    display: flex;
235}
236
237.lightbox-content {
238    max-width: 90vw;
239    max-height: 90vh;
240    object-fit: contain;
241}
242
243.lightbox-close {
244    position: absolute;
245    top: 1rem;
246    right: 1rem;
247    color: var(--text-light);
248    font-size: 2rem;
249    cursor: pointer;
250}
251
252/* Media Queries - Tablet */
253@media (min-width: 768px) {
254    /* Nawigacja dla tabletu */
255    .nav-toggle {
256        display: none;
257    }
258    
259    .nav-menu {
260        position: static;
261        display: flex;
262        flex-direction: row;
263        width: auto;
264    }
265    
266    .nav-menu li {
267        padding: 0 1rem;
268    }
269    
270    /* Hero dla tabletu */
271    .hero {
272        background-image: url('img/hero-tablet.jpg');
273    }
274    
275    /* Galeria - 2 kolumny */
276    .gallery {
277        grid-template-columns: repeat(2, 1fr);
278    }
279}
280
281/* Media Queries - Desktop */
282@media (min-width: 1024px) {
283    /* Hero dla desktop */
284    .hero {
285        background-image: url('img/hero-desktop.jpg');
286    }
287    
288    /* Galeria - 3 kolumny */
289    .gallery {
290        grid-template-columns: repeat(3, 1fr);
291        gap: 1.5rem;
292    }
293    
294    /* Efekty hover tylko na desktop */
295    .gallery-overlay {
296        opacity: 0;
297        transform: translateY(0);
298        background: var(--overlay-dark);
299    }
300    
301    .gallery-item:hover .gallery-overlay {
302        opacity: 1;
303    }
304}
305
306/* Media Queries - Duże ekrany */
307@media (min-width: 1440px) {
308    .gallery {
309        grid-template-columns: repeat(4, 1fr);
310    }
311}
312
313/* Orientacja landscape dla mobile */
314@media (max-width: 768px) and (orientation: landscape) {
315    .hero {
316        height: 100vw;
317    }
318    
319    .hero-title {
320        font-size: 2rem;
321    }
322}
323
324/* High DPI displays */
325@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
326    .hero {
327        background-image: url('img/hero-mobile@2x.jpg');
328    }
329    
330    @media (min-width: 768px) {
331        .hero {
332            background-image: url('img/hero-tablet@2x.jpg');
333        }
334    }
335    
336    @media (min-width: 1024px) {
337        .hero {
338            background-image: url('img/hero-desktop@2x.jpg');
339        }
340    }
341}

Krok 3: Optymalizacja i najlepsze praktyki

Optymalizacja obrazów:

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>

Dodatkowe style dla wydajności:

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}

Kompletny przykład kodu

HTML (index.html):

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

Projekt jest w pełni funkcjonalny i gotowy do użycia jako portfolio fotografa lub może być łatwo dostosowany do innych celów.

Vai a CodeWorlds