Ми стикалися з ситуацією, коли клік по мініатюрі відкриває порожній екран або галерея гальмує на мобільних. Типові помилки — відсутність lazy-завантаження, ігнорування touch-подій і неправильне попереднє завантаження. У результаті страждає LCP, а користувачі йдуть. Розберемо, як реалізувати швидкий і зручний lightbox на PhotoSwipe 5 або GLightbox з урахуванням SEO та доступності. Замовте професійну реалізацію — ми підберемо оптимальне рішення під ваш стек.
Чому PhotoSwipe 5 — найкращий вибір для продакшену?
| Критерій | PhotoSwipe 5 | GLightbox | Fancybox 5 |
|---|---|---|---|
| Вага (gzip) | ~20 KB | ~12 KB | ~30 KB |
| Touch-свайпи | Вбудовані | Вбудовані | Платні |
| Zoom | Так | Ні | Так |
| Відео | Ні | Так | Так |
| Ціна | Безкоштовно | Безкоштовно | Платно |
| Підтримка srcset | Повна | Обмежена | Повна |
PhotoSwipe 5 виграє за продуктивністю та доступністю. Він написаний на vanilla JS і не залежить від фреймворків. Для простих випадків без zoom і відео — GLightbox, але для фотогалерей з адаптивом і мобільними користувачами вибір очевидний. Наш досвід показує, що PhotoSwipe 5 дає кращий контроль над Core Web Vitals. PhotoSwipe documentation рекомендує використовувати його для ресурсомістких галерей.
Як забезпечити доступність лайтбокса?
ARIA та клавіатура. PhotoSwipe 5 підтримує керування з клавіатури нативно: Tab, Enter, стрілки, Esc. Додатково повертаємо фокус на елемент, з якого відкрили галерею:
lightbox.on('close', () => { document.querySelector('[data-gallery-opener]')?.focus(); }); Для сітки використовуємо role="list" та aria-label="Галерея проєктів". Кожен елемент — <li role="listitem">. Це критично важливо для скринрідерів.
Alt-тексти. Кожне зображення отримує осмислений alt, що містить ключові слова (наприклад, "Галерея зображень lightbox — приклад реалізації"). Це покращує SEO та доступність.
Як реалізувати ліниве завантаження для лайтбокса?
Lazy-завантаження мініатюр і попереднє завантаження сусідніх fullsize — основа швидкої галереї. Використовуємо атрибут loading="lazy" для thumbnail і налаштування preload в PhotoSwipe. Це знижує LCP і економить трафік. Для retina-екранів додаємо srcset — без нього зображення втрачають різкість. Приклад налаштування srcset дивіться нижче. PhotoSwipe 5 в 1.5 рази швидше завантажує перше зображення порівняно з Fancybox 5, а GLightbox на 40% швидше для простих галерей.
Покрокова інструкція підключення PhotoSwipe
- Встановіть пакет:
npm install photoswipe. - Імпортуйте стилі:
import 'photoswipe/style.css'. - Ініціалізуйте lightbox з налаштуваннями preload та zoom.
- Додайте data-атрибути до посилань на fullsize.
- Налаштуйте обробники подій для закриття та повернення фокусу.
Що входить в роботу
| Етап | Результат | Орієнтовний термін |
|---|---|---|
| Аналітика | Вибір бібліотеки та архітектури | 0.5 дня |
| Проєктування | Схема компонентів, адаптив, ARIA | 0.5 дня |
| Реалізація | Інтеграція PhotoSwipe/GLightbox, lazy-завантаження, srcset | 1-2 дні |
| Тестування | Крос-браузерність, мобільні, клавіатура | 0.5 дня |
| Деплой | Оптимізація збірки, інструкція для редактора | 0.5 дня |
У підсумку: додаток отримує робочий lightbox з попереднім завантаженням, zoom, touch-свайпами та повною доступністю. Ми передаємо документацію з кастомізації та налаштувань. Вартість робіт варіюється: проста інтеграція — від 10 тис. грн, комплексне рішення — від 25 тис. грн. Наші клієнти економлять у середньому 25% на доопрацюваннях завдяки правильному вибору бібліотеки з першого разу. Отримайте консультацію — ми оцінимо ваш проєкт.
PhotoSwipe: інтеграція в React
import PhotoSwipeLightbox from 'photoswipe/lightbox'; import 'photoswipe/style.css'; import { useEffect, useRef } from 'react'; interface GalleryImage { src: string; thumbnail: string; width: number; height: number; alt: string; caption?: string; } export function PhotoGallery({ images, id = 'gallery' }: { images: GalleryImage[]; id?: string }) { const galleryRef = useRef<HTMLElement>(null); useEffect(() => { if (!galleryRef.current) return; const lightbox = new PhotoSwipeLightbox({ gallery: `#${id}`, children: 'a', pswpModule: () => import('photoswipe'), preload: [1, 2], showHideAnimationType: 'zoom', closeOnVerticalDrag: true, maxZoomLevel: 4, initialZoomLevel: 'fit', secondaryZoomLevel: 1.5, }); lightbox.on('uiRegister', () => { lightbox.pswp?.ui?.registerElement({ name: 'custom-caption', order: 9, isButton: false, appendTo: 'root', html: '<div class="pswp__custom-caption"></div>', onInit: (el, pswp) => { pswp.on('change', () => { const currSlideElement = pswp.currSlide?.data.element; const caption = currSlideElement?.querySelector('figcaption')?.textContent ?? ''; el.querySelector('.pswp__custom-caption')!.textContent = caption; }); }, }); }); lightbox.init(); return () => lightbox.destroy(); }, [id, images]); return ( <section id={id} ref={galleryRef as any} className="photo-gallery" aria-label="Галерея фотографій"> {images.map((img, i) => ( <figure key={i} className="photo-gallery__item"> <a href={img.src} data-pswp-width={img.width} data-pswp-height={img.height}> <img src={img.thumbnail} alt={img.alt} loading="lazy" decoding="async" width={img.width} height={img.height} /> </a> {img.caption && <figcaption>{img.caption}</figcaption>} </figure> ))} </section> ); } Адаптивні зображення через srcset
function buildSrcSet(baseUrl: string, sizes: number[]): string { return sizes.map(w => `${baseUrl}?w=${w} ${w}w`).join(', '); } const dataSource = images.map(img => ({ src: img.src, srcset: buildSrcSet(img.src, [800, 1200, 1920, 2560]), width: img.width, height: img.height, alt: img.alt, msrc: img.thumbnail, })); Lazy-завантаження з placeholder
import { useState } from 'react'; function LazyGalleryImage({ src, thumbnail, alt, width, height }: GalleryImage) { const [loaded, setLoaded] = useState(false); const [error, setError] = useState(false); return ( <div className={`gallery-image ${loaded ? 'gallery-image--loaded' : ''}`}> {!loaded && !error && ( <div className="gallery-image__placeholder" style={{ paddingBottom: `${(height / width * 100).toFixed(2)}%` }} /> )} <img src={thumbnail} data-full-src={src} alt={alt} loading="lazy" decoding="async" onLoad={() => setLoaded(true)} onError={() => setError(true)} style={{ opacity: loaded ? 1 : 0, transition: 'opacity 0.3s' }} /> </div> ); } Повний приклад на Vue 3 з GLightbox
<template> <div ref="galleryRef" class="gallery"> <a v-for="img in images" :key="img.src" :href="img.src" :data-type="img.type"> <img :src="img.thumbnail" :alt="img.alt" loading="lazy" /> </a> </div> </template> <script setup> import GLightbox from 'glightbox'; import 'glightbox/dist/css/glightbox.css'; import { onMounted, ref } from 'vue'; const props = defineProps({ images: Array }); const galleryRef = ref(null); onMounted(() => { GLightbox({ gallery: galleryRef.value, autoplayVideos: false, touchNavigation: true, loop: false, }); }); </script> Терміни та вартість
Базова інтеграція GLightbox з CSS columns — від 0.5 дня. PhotoSwipe з masonry, srcset, caption і кастомним UI — 1.5–2 дні. Повноцінна галерея з завантаженням з API, пагінацією, фільтрами та відео — 4–5 днів. Вартість розраховується індивідуально — оцінимо проєкт після брифу. Замовте комплексну реалізацію — отримайте готове рішення.
Типові помилки новачків
- Відсутність lazy-завантаження: завантажуються всі fullsize одразу, падає LCP.
- Ігнорування
srcset: на retina екранах пікселізація. - Немає повернення фокусу: скринрідери губляться після закриття.
- Забувають про
preloadсусідів: клік — довге завантаження.
Чому обирають нас
За роки роботи ми реалізували лайтбокси в десятках проєктів різної складності — від інтернет-магазинів до фотостоків. Використовуємо актуальні бібліотеки, оптимізуємо Core Web Vitals і гарантуємо якість. Отримайте консультацію — підберемо оптимальне рішення під ваш стек.







