Create a responsive photo gallery with lightbox and filtering capabilities.
This project will guide you through creating a modern photo gallery using CSS Grid and Flexbox. You'll learn to design responsive image layouts with hover effects and lightbox functionality.
Intermediate: 2-3 hours | Advanced: 1-2 hours
Difficulty Level: Intermediate
<div class="gallery-container">
<div class="gallery-header">
<h1>Photo Gallery</h1>
<div class="gallery-filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="nature">Nature</button>
<button class="filter-btn" data-filter="city">City</button>
<button class="filter-btn" data-filter="people">People</button>
</div>
</div>
<div class="gallery-grid">
<div class="gallery-item" data-category="nature">
<img src="nature1.jpg" alt="Nature Scene">
<div class="gallery-overlay">
<h3>Mountain View</h3>
<p>Beautiful mountain landscape</p>
<button class="view-btn"><i class="fas fa-eye"></i></button>
</div>
</div>
<div class="gallery-item" data-category="city">
<img src="city1.jpg" alt="City Scene">
<div class="gallery-overlay">
<h3>City Lights</h3>
<p>Urban nightscape</p>
<button class="view-btn"><i class="fas fa-eye"></i></button>
</div>
</div>
<!-- Add more gallery items -->
</div>
</div>
<!-- Lightbox Modal -->
<div class="lightbox" id="lightbox">
<div class="lightbox-content">
<span class="close-btn">×</span>
<img src="" alt="" id="lightbox-img">
<div class="lightbox-info">
<h3 id="lightbox-title"></h3>
<p id="lightbox-description"></p>
</div>
</div>
</div>
.gallery-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.gallery-header {
text-align: center;
margin-bottom: 3rem;
}
.gallery-header h1 {
font-size: 2.5rem;
margin-bottom: 1.5rem;
color: #333;
}
.gallery-filters {
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
}
.filter-btn {
background: white;
border: 2px solid #e9ecef;
padding: 0.75rem 1.5rem;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
}
.filter-btn.active,
.filter-btn:hover {
background: #667eea;
color: white;
border-color: #667eea;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.gallery-item {
position: relative;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: transform 0.3s;
}
.gallery-item:hover {
transform: translateY(-5px);
}
.gallery-item img {
width: 100%;
height: 250px;
object-fit: cover;
display: block;
}
.gallery-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
padding: 2rem 1.5rem 1.5rem;
transform: translateY(100%);
transition: transform 0.3s;
}
.gallery-item:hover .gallery-overlay {
transform: translateY(0);
}
.gallery-overlay h3 {
margin: 0 0 0.5rem 0;
font-size: 1.2rem;
}
.gallery-overlay p {
margin: 0 0 1rem 0;
opacity: 0.9;
}
.view-btn {
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
transition: background 0.3s;
}
.view-btn:hover {
background: rgba(255,255,255,0.3);
}
/* Lightbox Styles */
.lightbox {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
z-index: 1000;
}
.lightbox.active {
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
position: relative;
max-width: 90%;
max-height: 90%;
}
.close-btn {
position: absolute;
top: -40px;
right: 0;
color: white;
font-size: 2rem;
cursor: pointer;
background: none;
border: none;
}
#lightbox-img {
max-width: 100%;
max-height: 70vh;
border-radius: 8px;
}
.lightbox-info {
color: white;
text-align: center;
margin-top: 1rem;
}
@media (max-width: 768px) {
.gallery-grid {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.gallery-filters {
gap: 0.5rem;
}
.filter-btn {
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
}
class PhotoGallery {
constructor() {
this.currentFilter = 'all';
this.init();
}
init() {
this.bindEvents();
}
bindEvents() {
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
this.setFilter(e.target.dataset.filter);
});
});
// Gallery items
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
this.openLightbox(e.target.closest('.gallery-item'));
});
});
// Close lightbox
document.querySelector('.close-btn').addEventListener('click', () => {
this.closeLightbox();
});
// Close on background click
document.getElementById('lightbox').addEventListener('click', (e) => {
if (e.target.id === 'lightbox') {
this.closeLightbox();
}
});
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.closeLightbox();
}
});
}
setFilter(filter) {
this.currentFilter = filter;
// Update active button
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.filter === filter);
});
// Filter gallery items
document.querySelectorAll('.gallery-item').forEach(item => {
const category = item.dataset.category;
const shouldShow = filter === 'all' || category === filter;
if (shouldShow) {
item.style.display = 'block';
item.style.animation = 'fadeIn 0.5s ease-in';
} else {
item.style.display = 'none';
}
});
}
openLightbox(galleryItem) {
const img = galleryItem.querySelector('img');
const title = galleryItem.querySelector('h3').textContent;
const description = galleryItem.querySelector('p').textContent;
document.getElementById('lightbox-img').src = img.src;
document.getElementById('lightbox-img').alt = img.alt;
document.getElementById('lightbox-title').textContent = title;
document.getElementById('lightbox-description').textContent = description;
document.getElementById('lightbox').classList.add('active');
document.body.style.overflow = 'hidden';
}
closeLightbox() {
document.getElementById('lightbox').classList.remove('active');
document.body.style.overflow = 'auto';
}
}
// Add CSS animation
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
`;
document.head.appendChild(style);
new PhotoGallery();