Реалізація галереї зображень (Lightbox) на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація галереї зображень (Lightbox) на сайті
Проста
~2-3 робочих дні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація галереї зображень (Lightbox) на вебсайті

Галерея з lightbox — один з найчастіших UI-компонентів. Задача здається простою, поки не зіткнешся з touch-свайпами, lazy-завантаженням, zoom, передзавантаженням сусідніх зображень та доступністю.

Бібліотеки

PhotoSwipe 5 — найкращий варіант для більшості задач. Чистий vanilla JS, touch-first, zoom, підтримка srcset. Активно підтримується, ~20 KB gzip.

GLightbox — легкавесна (~12 KB), підтримує відео та iframe в лайтбоксі, без залежностей.

Fancybox 5 — багатий функціонал, але платна для комерційних проектів.

Для простих випадків без zoom і відео — GLightbox. Для повнофункціональної фотогалереї — PhotoSwipe.

PhotoSwipe: інтеграція

npm install photoswipe
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

// Генеруємо srcset для лайтбокса — різні розміри для різних екранів
function buildSrcSet(baseUrl: string, sizes: number[]): string {
  return sizes.map(w => `${baseUrl}?w=${w} ${w}w`).join(', ')
}

// У PhotoSwipe 5 — через dataSource
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,
  // Для мобільних — не грузити 2560px
  msrc: img.thumbnail,  // placeholder до завантаження повного
}))

Masonry-лейаут сітки

/* CSS Columns — найпростіше masonry без JS */
.photo-gallery {
  columns: 3 200px;
  column-gap: 8px;
}

.photo-gallery__item {
  break-inside: avoid;
  margin-bottom: 8px;
}

.photo-gallery__item img {
  width: 100%;
  height: auto;
  display: block;
}

@media (max-width: 768px) {
  .photo-gallery { columns: 2 150px; }
}

@media (max-width: 480px) {
  .photo-gallery { columns: 1; }
}

Для підтримки CSS Masonry (поки тільки Firefox за флагом) або точного вирівнювання рядків — Masonry.js або нативний CSS grid з grid-template-rows: masonry.

Рівномірна сітка з відомими пропорціями

/* Якщо співвідношення сторін відомо заздалегідь */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 8px;
}

.gallery-grid__item {
  aspect-ratio: 4/3;
  overflow: hidden;
}

.gallery-grid__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

.gallery-grid__item:hover img {
  transform: scale(1.05);
}

Lazy-завантаження з плейсхолдером

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' : ''}`}>
      {/* LQIP (Low Quality Image Placeholder) */}
      {!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>
  )
}

GLightbox для відео-галереї

npm install glightbox
import GLightbox from 'glightbox'
import 'glightbox/dist/css/glightbox.css'

const lightbox = GLightbox({
  elements: [
    {
      href: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
      type: 'video',
      source: 'youtube',
      width: 900,
    },
    {
      href: '/video/promo.mp4',
      type: 'video',
      description: 'Промо-ролик',
    },
    {
      href: '/images/photo1.jpg',
      type: 'image',
      description: 'Опис фото',
    },
  ],
  autoplayVideos: false,
  loop: false,
  draggable: true,
  dragToleranceX: 40,
  dragToleranceY: 65,
  swipeToClose: true,
})

Навігація клавіатурою та доступність

// PhotoSwipe підтримує клавіатуру нативно
// Додатково: повернення фокусу при закриття
lightbox.on('close', () => {
  // Повертаємо фокус на елемент, з якого ми його відкривали
  const opener = document.querySelector('[data-gallery-opener]') as HTMLElement
  opener?.focus()
})

// ARIA для сітки
// role="list" + role="listitem" або просто нативний <ul><li>
<ul class="photo-gallery" role="list" aria-label="Галерея проектів">
  <li role="listitem">
    <a href="/large/1.jpg"
       aria-label="Відкрити зображення: Проект офісу, 2024"
       data-pswp-width="2400"
       data-pswp-height="1600">
      <img src="/thumb/1.jpg" alt="Проект офісу, 2024" loading="lazy">
    </a>
  </li>
</ul>

Терміни

GLightbox з базовою сіткою та CSS columns — половина дня. PhotoSwipe з masonry, srcset, caption та кастомним UI — 1,5–2 дні. Повнофункціональна галерея з завантаженням з API, пагінацією, фільтрами по категоріях та підтримкою відео — 4–5 днів.