- Plan i struktura: minimalny HTML oraz przygotowanie obrazów
- Wymagania i porządkowanie plików
- Najprostszy szkielet HTML
- Opis alternatywny i semantyka
- Miniatury, proporcje i spójność
- Styl CSS i układ: Grid, responsywność i dopracowanie
- Reset, zmienne i fundament
- Siatka CSS Grid z auto-fit
- Dopasowanie obrazu: wypełnienie i bezpieczne przycięcie
- Stany najechania i fokus klawiatury
- Tryb jasny/ciemny i kontrast
- Interakcja bez bibliotek: lightbox, klawiatura i gesty
- Modal oparty o czysty CSS (szybki start)
- Wersja JavaScript: otwieranie, zamykanie, nawigacja
- Pułapki fokusa i pułapka klawiatury
- Gesty dotyku i przewijanie
- Wydajność i SEO: optymalizacja obrazów, lazy-loading i cache
- Format, kompresja i balans jakości
- Responsive images: srcset i sizes
- Lazy loading i LCP/CLS
- Cache HTTP, CDN i wersjonowanie
- Preload i preconnect
- Dostępność i integracja z CMS: WordPress, Jamstack i automatyzacja
- Role, etykiety i kontrasty
- WordPress bez wtyczek
- Jamstack: Eleventy/Hugo i generowanie miniaturek
- Narzędzia CLI: sharp/imagemin
- Testy jakości: lista kontrolna
- Rozszerzenia i utrzymanie
Własna galeria zdjęć bez wtyczek to pełna kontrola nad kodem, szybkością i wyglądem. W tym poradniku zbudujesz od podstaw lekki komponent, który działa w każdej nowoczesnej przeglądarce, jest łatwy do utrzymania i rozszerzania. Przejdziemy przez planowanie, strukturę HTML, styl CSS, interakcje w czystym JS, a także optymalizację, tak by obrazy ładowały się błyskawicznie i wyglądały świetnie na każdym ekranie. Zero zbędnych bibliotek – tylko zrozumiały, przewidywalny kod.
Plan i struktura: minimalny HTML oraz przygotowanie obrazów
Wymagania i porządkowanie plików
Zanim napiszesz choć jedną linijkę, uporządkuj zasoby. Przygotuj katalogi: <project/>, w nim <img/> na obrazy i <assets/> na style oraz skrypty. Dobre nazewnictwo plików ułatwia automatyzację i wersjonowanie. Stosuj spójną konwencję: np. <img/portret-01-800.jpg> dla wersji średniej, <img/portret-01-1600.jpg> dla dużej oraz <img/portret-01-320.jpg> dla miniatury. Dzięki temu szybko podmienisz ścieżki lub wygenerujesz warianty skryptem.
Zadbaj o metadane i opisy. Każde zdjęcie powinno mieć krótki, rzeczowy opis alternatywny (atrybut alt), zgodny z kontekstem publikacji. To zwiększa dostępność i ułatwia nawigację osobom korzystającym z czytników ekranu, a jednocześnie pomaga wyszukiwarkom lepiej zrozumieć treść.
Najprostszy szkielet HTML
Podstawowa struktura może wyglądać tak (zwróć uwagę na semantykę linku do dużego pliku i miniaturę w elemencie img):
<div class="gallery">
<a class="gallery__item" href="img/portret-01-1600.jpg" data-title="Portret 01">
<img src="img/portret-01-320.jpg" alt="Portret w świetle dziennym" width="320" height="213" loading="lazy">
</a>
<a class="gallery__item" href="img/portret-02-1600.jpg" data-title="Portret 02">
<img src="img/portret-02-320.jpg" alt="Portret w cieniu drzew" width="320" height="213" loading="lazy">
</a>
</div>
Wariant ten jest lekki, atrybut loading ułatwia lazy-loading, a dane opisujące (np. data-title) przydadzą się w modalu. Klasami nazwij logikę: "gallery" dla kontenera, "gallery__item" dla elementu klikalnego. Jeśli przewidujesz podpisy, możesz dodać <span class="gallery__caption"> wewnątrz linku (ale niech nie przeszkadza na urządzeniach dotykowych).
Opis alternatywny i semantyka
Atrybut alt opisuje treść, nie czynność. Unikaj pustych słów; zamiast "Zdjęcie 1" użyj "Mężczyzna na tle ceglanej ściany". Dobra semantyka to nie tylko aspekt etyczny i prawny, ale również korzyść w pozycjonowaniu. Jeśli obraz jest dekoracyjny, alt może być pusty, ale w galerii artystycznej zwykle jest sensowny opis.
Miniatury, proporcje i spójność
Miniatury powinny mieć stały stosunek boków (np. 3:2 lub 1:1), by siatka była równa. Zadbaj o atrybuty width i height, aby zredukować skoki układu (CLS) podczas ładowania. Dzięki temu przeglądarka zarezerwuje miejsce jeszcze przed pobraniem obrazu, co wspiera wydajność i komfort przewijania.
Styl CSS i układ: Grid, responsywność i dopracowanie
Reset, zmienne i fundament
Najpierw krótki reset i zmienne kolorystyczne. W arkuszu <assets/styles.css> zacznij od:
* { box-sizing: border-box; }
img { display: block; max-width: 100%; height: auto; }
:root { –gap: 12px; –bg: #0e0e0f; –fg: #f2f2f2; –muted: #9aa0a6; }
To zapewnia przewidywalność wymiarów i podstawową paletę. Później łatwo podmienisz kolory lub rozmiary przerw.
Siatka CSS Grid z auto-fit
Wprowadź elastyczną siatkę, która sama układa kafelki w zależności od szerokości kontenera:
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: var(–gap);
margin: 0 auto;
padding: var(–gap);
max-width: 1200px;
background: var(–bg);
color: var(–fg);
}
.gallery__item {
position: relative;
overflow: hidden;
border-radius: 8px;
outline: none;
}
Dzięki Grid i minmax otrzymujesz płynny układ dla telefonów, tabletów i desktopów — bez media queries albo z ich minimalnym udziałem. To solidna baza pod responsywność.
Dopasowanie obrazu: wypełnienie i bezpieczne przycięcie
Jeśli miniatury mają różne proporcje, kontroluj kadr przez kontener o wymuszonych proporcjach i obraz w trybie "okładki":
.gallery__item::before { content: ""; display: block; padding-bottom: 66.666%; }
.gallery__item img {
position: absolute; inset: 0;
width: 100%; height: 100%;
object-fit: cover;
transition: transform .3s ease;
}
Takie podejście trzyma siatkę w ryzach i poprawia wrażenia wizualne, nawet gdy źródłowe zdjęcia są niespójne.
Stany najechania i fokus klawiatury
Niech element daje subtelny sygnał interakcji:
.gallery__item:hover img, .gallery__item:focus img { transform: scale(1.03); }
.gallery__item:focus { outline: 2px solid #6ab0ff; outline-offset: 2px; }
Dzięki temu użytkownicy klawiatury natychmiast widzą, gdzie znajduje się fokus, co sprzyja standardom WCAG.
Tryb jasny/ciemny i kontrast
Dodaj automatyczny dobór motywu na podstawie preferencji systemowych:
@media (prefers-color-scheme: light) {
:root { –bg: #ffffff; –fg: #1b1b1b; –muted: #555; }
}
Następnie sprawdź kontrast kluczowych elementów (np. podpisów) i w razie potrzeby zwiększ nasycenie barw lub rozmiary czcionek.
Interakcja bez bibliotek: lightbox, klawiatura i gesty
Modal oparty o czysty CSS (szybki start)
Jeśli chcesz natychmiastowego efektu, użyj "target hack": każdy element ma unikatowy identyfikator, a klik ustawionego linku otwiera warstwę pełnoekranową. To minimum bez JavaScript, choć z ograniczeniami nawigacji i dostępności. Lepszym wyborem będzie jednak wariant JS poniżej.
Wersja JavaScript: otwieranie, zamykanie, nawigacja
Stwórz prosty modal i logikę sterującą. W HTML dodaj na końcu body (lub w miejscu, w którym renderujesz komponent) kontener modalu:
<div class="lightbox" hidden>
<button class="lightbox__close" aria-label="Zamknij">×</button>
<button class="lightbox__prev" aria-label="Poprzednie">‹</button>
<button class="lightbox__next" aria-label="Następne">›</button>
<img class="lightbox__img" alt="">
<div class="lightbox__caption"></div>
</div>
Style dla modalu (zachowaj skróconą formę):
.lightbox { position: fixed; inset: 0; display: grid; place-items: center; background: rgba(0,0,0,.9); z-index: 1000; }
.lightbox[hidden] { display: none; }
.lightbox__img { max-width: 90vw; max-height: 80vh; }
.lightbox__caption { color: #fff; margin-top: 8px; text-align: center; }
Skrypt w czystym JS (załaduj go na końcu dokumentu lub jako type="module"):
const items = […document.querySelectorAll(".gallery__item")];
const lb = document.querySelector(".lightbox");
const lbImg = lb.querySelector(".lightbox__img");
const lbCap = lb.querySelector(".lightbox__caption");
let index = -1;
function open(i) { index = i; const a = items[i]; lbImg.src = a.href; lbImg.alt = a.querySelector("img").alt; lbCap.textContent = a.dataset.title || ""; lb.hidden = false; lb.focus(); preload(i+1); preload(i-1); history.pushState({lb:true}, ""); }
function close() { lb.hidden = true; lbImg.src = ""; }
function next() { open((index + 1 + items.length) % items.length); }
function prev() { open((index – 1 + items.length) % items.length); }
items.forEach((a, i) => a.addEventListener("click", e => { e.preventDefault(); open(i); }));
lb.querySelector(".lightbox__close").addEventListener("click", close);
lb.querySelector(".lightbox__next").addEventListener("click", next);
lb.querySelector(".lightbox__prev").addEventListener("click", prev);
window.addEventListener("keydown", e => { if (lb.hidden) return; if (e.key === "Escape") close(); if (e.key === "ArrowRight") next(); if (e.key === "ArrowLeft") prev(); });
window.addEventListener("popstate", () => { if (!lb.hidden) close(); });
function preload(i) { if (i < 0 || i >= items.length) return; const img = new Image(); img.src = items[i].href; }
Ten zestaw zapewnia podstawy: otwieranie, zamykanie, obsługę strzałek, klawisza Escape, a nawet wstecz przeglądarki. Otrzymujesz w efekcie lekki lightbox bez bibliotek.
Pułapki fokusa i pułapka klawiatury
Dodaj pułapkę fokusa w modalu, by tab nie wychodził poza aktywne elementy. Zbierz focusable w modalu (przyciski) i przechwytuj zdarzenie keydown: gdy użytkownik jest na pierwszym i wciśnie Shift+Tab, skocz na ostatni; i odwrotnie. To poprawia dostępność w realnym użyciu.
Gesty dotyku i przewijanie
Na urządzeniach mobilnych możesz dodać prosty detektor przesunięcia (touchstart/touchend), by zmieniać zdjęcia ruchem palca. Ogranicz też przewijanie tła, gdy modal jest otwarty, np. przez dodanie klasy "no-scroll" na body i ustawienie overflow: hidden; podczas wyświetlania modalu.
Wydajność i SEO: optymalizacja obrazów, lazy-loading i cache
Format, kompresja i balans jakości
Generuj wersje WebP i AVIF dla nowoczesnych przeglądarek oraz zapasowy JPEG/PNG. Zadbaj o średnią wagę miniatur rzędu 20–50 KB i pełnych zdjęć 150–400 KB (w zależności od przeznaczenia). Mądrze dobrane parametry kompresji to kluczowa optymalizacja.
Responsive images: srcset i sizes
W miniaturach oraz pełnych zdjęciach używaj atrybutów <img srcset> i <sizes>. Dzięki temu przeglądarka pobierze najlepszy wariant dla faktycznej szerokości. Przykład miniatury:
<img src="img/portret-01-320.jpg"
srcset="img/portret-01-320.jpg 320w, img/portret-01-480.jpg 480w, img/portret-01-640.jpg 640w"
sizes="(max-width: 600px) 45vw, (max-width: 1200px) 30vw, 200px"
alt="Portret w świetle dziennym" width="320" height="213" loading="lazy">
To znacząco poprawia wydajność. Ustalanie sizes nie musi być idealne – wystarczy dopasować je do typowego układu siatki.
Lazy loading i LCP/CLS
Native "loading=lazy" ogranicza transfer, ale nie stosuj go do pierwszych zdjęć widocznych w obszarze ekranu (Above the Fold), bo może to pogorszyć LCP. Pamiętaj o atrybutach width/height, by uniknąć przeskoków układu. Regularnie mierz metryki w Lighthouse lub WebPageTest. Świadoma optymalizacja to stały proces, a nie jednorazowe działanie.
Cache HTTP, CDN i wersjonowanie
Dla statycznych obrazów ustaw długie nagłówki cache-control z fingerprintem w nazwie, np. "portret-01-320.6f3a1.jpg". Pliki serwuj z CDN z włączonym HTTP/2 lub HTTP/3. Dla CSS/JS włącz minifikację i treeshaking. Razem daje to wymierny zysk dla SEO i zadowolenia użytkowników.
Preload i preconnect
Jeżeli modal często otwierasz po kliknięciu w miniaturę, rozważ <link rel="preload" as="image"> dla pierwszych fotografii lub dynamiczne preload w JS tuż po interakcji użytkownika. Dla CDN użyj <link rel="preconnect">, by skrócić czas ustanawiania połączenia.
Dostępność i integracja z CMS: WordPress, Jamstack i automatyzacja
Role, etykiety i kontrasty
Kontener modalu może mieć aria-modal="true", a przyciski aria-label z jasnymi komunikatami. Sprawdź czytelność podpisów i wielkość elementów dotykowych (co najmniej 44×44 px). Troska o dostępność i kontrast wpływa na realną użyteczność, nie tylko na raporty.
WordPress bez wtyczek
Jeśli używasz WordPressa, masz kilka ścieżek bez dodawania pluginów:
- Blok HTML w edytorze: wklej strukturę <div class="gallery"> … i wczytaj własny arkusz stylów w funkcjach motywu (wp_enqueue_style) oraz skrypt (wp_enqueue_script, w trybie defer). To najprostsze.
- Shortcode: dodaj w functions.php prosty rejestrator [simple_gallery], który wygeneruje markup z listy ID mediów. Dane wyciągniesz przez wp_get_attachment_image_src i wp_get_attachment_caption.
- Pattern: przygotuj wzorzec bloku, by redaktorzy mogli dodawać gotową siatkę jednym kliknięciem, bez ryzyka psucia HTML.
Pamiętaj, by w galeriach WP uzupełniać alt i podpisy w bibliotece mediów, co wspiera SEO i spójność treści.
Jamstack: Eleventy/Hugo i generowanie miniaturek
W statycznych generatorach użyj pipeline’u do obrazów: np. Eleventy Image lub wtyczki Hugo do tworzenia wariantów 320/480/640/1600. Szablon wygeneruje atrybuty srcset i sizes automatycznie. Ten proces znacząco upraszcza utrzymanie wielojęzycznych i wieloformatowych galerii.
Narzędzia CLI: sharp/imagemin
W projektach bez CMS dodaj do package.json skrypty: "images:thumbs" z użyciem "sharp" do generowania miniatur i "images:opt" z "imagemin" oraz pluginami do WebP/AVIF. Zautomatyzuj nazewnictwo, aby utrzymać porządek i uniknąć ręcznej obróbki setek plików.
Testy jakości: lista kontrolna
- Czy każde zdjęcie ma sensowny alt i poprawny podpis? (także pod kątem WCAG)
- Czy metryki LCP/CLS są stabilne na 3G/4G? (ważne dla wydajność)
- Czy modal zamyka się klawiszem Escape i nie gubi fokusa? (kluczowa dostępność)
- Czy siatka skaluje się na małych ekranach i dużych monitorach? (pełna responsywność)
- Czy obrazy mają odpowiednie warianty i poprawne srcset? (lepsza optymalizacja)
Rozszerzenia i utrzymanie
Rozważ filtrowanie po tagach, paginację i wirtualizację (render tylko widocznych elementów) przy bardzo dużych kolekcjach. Monitoruj błędy w konsoli i w narzędziach analitycznych. Portuj kod do komponentów (np. Web Components lub lekkie frameworki), ale bez utraty kontroli nad podstawowym HTML/CSS/JS.
Tworząc galerię bez wtyczek, zyskujesz przewidywalny skład kodu, który jest prosty do audytu i zgodny z najlepszymi praktykami: od dostępności, przez optymalizacja obrazów i responsywność, po mierzalne korzyści dla SEO. Jeśli potrzebujesz pełnego minimum copy-paste, wystarczy połączyć przedstawione fragmenty HTML, CSS i JS, a następnie dopasować ścieżki obrazów, podpisy i parametry siatki do własnych potrzeb.