We use cookies to enhance your experience on the site
CodeWorlds

Main Project - Responsive Photographer Portfolio Gallery

In this project, we'll create a professional, fully responsive portfolio gallery for a photographer. We'll use all the techniques from this module: media queries, responsive units (vw, vh), viewport, image optimization, and the mobile-first approach. The gallery will include navigation, a hero section, a photo grid with hover effects, category filtering, and a lightbox for viewing photos.

Project description

Our portfolio gallery will have the following features:

  • Responsive navigation - hamburger menu on mobile devices
  • Hero Section - full-screen using viewport units
  • Photo gallery - responsive grid with hover effects
  • Category filtering - filter buttons with interactive design
  • Lightbox - viewing enlarged photos
  • Image optimization - different sizes for different devices

Step 1: HTML Structure with Mobile-First Approach

We start by creating the HTML structure with mobile devices in mind:

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>John Smith - Photographer Portfolio</title>
7    <link rel="stylesheet" href="styles.css">
8</head>
9<body>
10    <!-- Navigation -->
11    <nav class="navbar">
12        <div class="nav-container">
13            <div class="nav-logo">JS 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">Home</a></li>
21                <li><a href="#portfolio">Portfolio</a></li>
22                <li><a href="#about">About</a></li>
23                <li><a href="#contact">Contact</a></li>
24            </ul>
25        </div>
26    </nav>
27
28    <!-- Hero Section -->
29    <section id="home" class="hero">
30        <div class="hero-content">
31            <h1 class="hero-title">John Smith</h1>
32            <p class="hero-subtitle">Photography is my passion</p>
33            <a href="#portfolio" class="hero-button">View portfolio</a>
34        </div>
35        <div class="hero-overlay"></div>
36    </section>
37
38    <!-- Portfolio Section -->
39    <section id="portfolio" class="portfolio">
40        <div class="container">
41            <h2 class="section-title">My Portfolio</h2>
42
43            <!-- Category filters -->
44            <div class="filter-buttons">
45                <button class="filter-btn active" data-filter="all">All</button>
46                <button class="filter-btn" data-filter="nature">Nature</button>
47                <button class="filter-btn" data-filter="portrait">Portraits</button>
48                <button class="filter-btn" data-filter="architecture">Architecture</button>
49            </div>
50
51            <!-- Gallery -->
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="Mountain landscape">
58                    </picture>
59                    <div class="gallery-overlay">
60                        <h3>Mountain landscape</h3>
61                        <p>Nature</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="Woman portrait">
70                    </picture>
71                    <div class="gallery-overlay">
72                        <h3>Portrait</h3>
73                        <p>Portraits</p>
74                    </div>
75                </div>
76
77                <!-- More gallery items... -->
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>

Step 2: Mobile-First CSS with Responsive Units

We start styling from mobile devices:

1/* Reset and basic styles */
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/* Navigation - 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 for 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 with viewport units */
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/* Filters */
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/* Gallery - 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    /* Navigation for tablet */
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 for tablet */
271    .hero {
272        background-image: url('img/hero-tablet.jpg');
273    }
274
275    /* Gallery - 2 columns */
276    .gallery {
277        grid-template-columns: repeat(2, 1fr);
278    }
279}
280
281/* Media Queries - Desktop */
282@media (min-width: 1024px) {
283    /* Hero for desktop */
284    .hero {
285        background-image: url('img/hero-desktop.jpg');
286    }
287
288    /* Gallery - 3 columns */
289    .gallery {
290        grid-template-columns: repeat(3, 1fr);
291        gap: 1.5rem;
292    }
293
294    /* Hover effects only on 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 - Large screens */
307@media (min-width: 1440px) {
308    .gallery {
309        grid-template-columns: repeat(4, 1fr);
310    }
311}
312
313/* Landscape orientation for 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}

Step 3: Optimization and best practices

Image optimization:

1<!-- Use the picture element for responsive images -->
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="Photo description"
19         loading="lazy">
20</picture>

Additional styles for performance:

1/* Use will-change for animated elements */
2.gallery-item {
3    will-change: transform;
4}
5
6/* Performance optimization */
7.gallery-item img {
8    will-change: transform;
9    backface-visibility: hidden;
10}
11
12/* Use 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}

Complete code example

HTML (index.html):

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <meta name="description" content="Photography portfolio of John Smith">
7    <title>John Smith - Photographer Portfolio</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    <!-- Navigation -->
14    <nav class="navbar">
15        <div class="nav-container">
16            <div class="nav-logo">JS 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">Home</a></li>
24                <li><a href="#portfolio">Portfolio</a></li>
25                <li><a href="#about">About</a></li>
26                <li><a href="#contact">Contact</a></li>
27            </ul>
28        </div>
29    </nav>
30
31    <!-- Hero Section -->
32    <section id="home" class="hero">
33        <div class="hero-content">
34            <h1 class="hero-title">John Smith</h1>
35            <p class="hero-subtitle">Photography is my passion</p>
36            <a href="#portfolio" class="hero-button">View portfolio</a>
37        </div>
38        <div class="hero-overlay"></div>
39    </section>
40
41    <!-- Portfolio Section -->
42    <section id="portfolio" class="portfolio">
43        <div class="container">
44            <h2 class="section-title">My Portfolio</h2>
45
46            <!-- Category filters -->
47            <div class="filter-buttons">
48                <button class="filter-btn active" data-filter="all">All</button>
49                <button class="filter-btn" data-filter="nature">Nature</button>
50                <button class="filter-btn" data-filter="portrait">Portraits</button>
51                <button class="filter-btn" data-filter="architecture">Architecture</button>
52            </div>
53
54            <!-- Gallery -->
55            <div class="gallery">
56                <!-- Sample gallery items -->
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="Mountain landscape">
62                    </picture>
63                    <div class="gallery-overlay">
64                        <h3>Mountain landscape</h3>
65                        <p>Nature</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="Portrait">
74                    </picture>
75                    <div class="gallery-overlay">
76                        <h3>Portrait</h3>
77                        <p>Portraits</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="Architecture">
86                    </picture>
87                    <div class="gallery-overlay">
88                        <h3>Modern architecture</h3>
89                        <p>Architecture</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="Forest">
98                    </picture>
99                    <div class="gallery-overlay">
100                        <h3>Forest path</h3>
101                        <p>Nature</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="Child portrait">
110                    </picture>
111                    <div class="gallery-overlay">
112                        <h3>Childhood joy</h3>
113                        <p>Portraits</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="Bridge">
122                    </picture>
123                    <div class="gallery-overlay">
124                        <h3>Bridge at sunset</h3>
125                        <p>Architecture</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>

Summary

This project demonstrates all the key concepts of responsive design:

  1. Mobile-First Approach - we start from mobile styles
  2. Media Queries - we adapt the layout for different devices
  3. Viewport Units (vw, vh) - for full-screen sections
  4. Responsive images - picture element with different sources
  5. CSS Grid - responsive gallery grid
  6. Flexbox - flexible navigation
  7. Clamp() - responsive font sizes
  8. Optimization - lazy loading, will-change, contain
  9. Accessibility - semantic HTML, aria-labels
  10. Modern features - dark mode, reduced motion

The project is fully functional and ready to use as a photographer's portfolio or can be easily adapted for other purposes.

Go to CodeWorlds