Пользователь покидает страницу, если не может рассмотреть детали товара. Задержка загрузки первого изображения на 1 секунду снижает конверсию на 20% — это факт, подтверждённый исследованиями. Галерея изображений — не просто набор картинок, а сложный инженерный компонент: управление большими файлами, зум без потери резкости, адаптация под тачскрины и оптимизация метрик Core Web Vitals. Мы разрабатываем такие решения для e-commerce, используя React 18, Next.js 14, Vue 3 или чистый HTML/CSS/JS. Наш pipeline обработки изображений снижает стоимость доставки контента на 40% и повышает скорость загрузки. Получите консультацию по вашей галерее, написав нам.
Почему зум влияет на конверсию?
Пользователь ожидает рассмотреть детали: ткань, швы, текстуру. Без зума он уходит к конкурентам. По данным Baymard Institute, качественные изображения повышают конверсию на 30%. Мы реализуем три типа зума: hover-zoom для десктопа (при наведении — увеличенная область), pinch-to-zoom для мобильных (жест двумя пальцами) и lightbox для полноэкранного просмотра. Каждый механизм адаптирован под устройство и не конфликтует с навигацией.
Как подготовить изображения для максимальной производительности?
Магазин должен хранить изображение в максимальном разрешении (2000–4000px по длинной стороне) и раздавать нужный размер под контекст. Раздавать оригинал 5MB на каждую страницу — грубая ошибка. Pipeline обработки при загрузке нового изображения:
Загрузка оригинала → S3 (originals/, приватный) ↓ Worker (libvips) Генерация вариантов: - thumbnail: 100×100, JPEG 80%, для тумбнейлов - small: 400×400, JPEG 85%, для списка товаров - medium: 800×800, JPEG 90%, для основного слота галереи - large: 1600×1600, JPEG 95%, для зума - webp: каждый вариант в WebP (обычно 30-50% меньше JPEG) ↓ CDN (публичный бакет или Cloudflare Images) libvips быстрее ImageMagick в 4–8 раз и потребляет значительно меньше памяти при обработке больших изображений. Для PHP — intervention/image с libvips-драйвером, для Node.js — sharp.
Современный <picture> с WebP:
<picture> <source srcset="product-800.webp" type="image/webp"> <img src="product-800.jpg" alt="галерея изображений товара — Nike Air Max 90" width="800" height="800" loading="lazy"> </picture> Рекомендуем использовать WebP для всех копий — это снижает трафик на 30–50%.
Структура компонента галереи
Типовая раскладка карточки товара: основной слот (большое изображение) + тумбнейл-полоса снизу или слева. На десктопе — горизонтальный или вертикальный strip, на мобайле — свайп по основному слоту.
function ProductGallery({ images, activeVariantImages }: Props) { const [activeIndex, setActiveIndex] = useState(0); const [isZoomed, setIsZoomed] = useState(false); // При смене варианта — сброс на первое изображение варианта useEffect(() => { setActiveIndex(0); }, [activeVariantImages]); const allImages = [...activeVariantImages, ...images.filter( img => !activeVariantImages.find(v => v.id === img.id) )]; return ( <div className="gallery"> <MainSlot image={allImages[activeIndex]} onZoom={() => setIsZoomed(true)} isZoomed={isZoomed} /> <Thumbnails images={allImages} activeIndex={activeIndex} onSelect={setActiveIndex} /> {isZoomed && ( <LightboxOverlay images={allImages} startIndex={activeIndex} onClose={() => setIsZoomed(false)} /> )} </div> ); } Реализация зума
Hover zoom
Классический паттерн для десктопа: при наведении курсора на изображение, рядом (или поверх) появляется увеличенная область. Техника реализации:
function useHoverZoom(containerRef: RefObject<HTMLDivElement>, scale = 2.5) { const [position, setPosition] = useState({ x: 0, y: 0 }); const [isHovering, setIsHovering] = useState(false); const handleMouseMove = (e: MouseEvent) => { const rect = containerRef.current!.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * 100; const y = ((e.clientY - rect.top) / rect.height) * 100; setPosition({ x, y }); }; return { position, isHovering, handlers: { onMouseMove: handleMouseMove, ... } }; } Отображение зума через CSS transform: scale() с transform-origin в точке курсора. Изображение для зума должно быть в 2–3 раза больше контейнера — иначе зум будет размытым.
| Тип зума | Устройства | Взаимодействие | Размер изображения для зума |
|---|---|---|---|
| Hover zoom | Десктоп | Наведение мыши | 1600×1600px (scale 2.5) |
| Pinch-to-zoom | Мобильные/планшеты | Два пальца | 2000×2000px+ (max 4x) |
| Lightbox | Все | Клик / кнопка | 2000×2000px+ (оригинал) |
Pinch-to-zoom для мобайла
На тачскрине hover-зум не работает. Нужен pinch-to-zoom и/или двойной тап для увеличения. React-zoom-pan-pinch — библиотека для этого. Поддерживает pinch, double tap, wheel zoom, panning:
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; <TransformWrapper minScale={1} maxScale={4} doubleClick={{ mode: 'zoomIn' }} > <TransformComponent> <img src={largeImageUrl} alt={alt} /> </TransformComponent> </TransformWrapper> Важно: при активном зуме горизонтальный свайп для смены изображения должен быть отключён — иначе конфликт жестов. Переключаем режим: пока scale > 1 — только pan, при scale === 1 — разрешаем свайп.
Lightbox / полноэкранный просмотр
При клике на изображение (десктоп) или кнопку «Полный экран» — открывается modal/lightbox с изображением на весь экран. Требования:
- Закрытие по Escape и клику за пределами
- Переключение стрелками клавиатуры (Left/Right)
- Swipe на мобайле
- Кнопки превью внизу
- URL не изменяется (или хеш:
#image-3)
Для lightbox используем PhotoSwipe (5kb gzip, отличная мобильная поддержка). PhotoSwipe работает нативно с touch events, имеет progressive loading (показывает thumb, пока грузится large).
Оптимизация загрузки и контент
Последовательная загрузка изображений
Нельзя грузить все изображения галереи сразу — это замедляет LCP. Стратегия:
- Первое изображение —
loading="eager",fetchpriority="high", preload в<head> - Тумбнейлы — маленькие файлы, грузим все сразу (они небольшие)
- Остальные large изображения —
loading="lazy"или загружаем только при переключении на тумбнейл
При переходе к изображению N — предзагружаем N+1 и N-1 (neighbour prefetch).
Поддержка видео в галерее
Многие магазины добавляют видео-обзор прямо в галерею — между изображениями. Тумбнейл видео — стоп-кадр с иконкой play поверх. При выборе — видео загружается lazily (<video preload="none">).
Форматы: MP4/H.264 (максимальная совместимость) + WebM/VP9 (меньший размер). Автопроигрывание — только muted, иначе браузер заблокирует.
Изображения по вариантам
Каждый вариант товара (цвет) имеет свой набор изображений. При выборе варианта:
- Галерея переключается на изображения этого варианта
- Плавная анимация перехода (crossfade или slide)
- Тумбнейл выбранного варианта подсвечивается
В БД: product_images (id, product_id, variant_id, url, sort_order). При variant_id IS NULL — общие изображения, показываются для всех вариантов.
Что входит в работу
- Анализ текущих изображений и подготовка рекомендаций по съёмке
- Разработка pipeline генерации превью (libvips + S3 + CDN)
- Создание компонента галереи с зумом и lightbox
- Настройка lazy load, preload и Core Web Vitals
- Интеграция с CMS (WordPress, Shopify, Laravel) и вариантами товара
- Тестирование на реальных устройствах (iPhone, Android, разные браузеры)
- Документация по использованию и поддержка после запуска
Типичные ошибки при разработке галереи
- Использование одного размера изображений для всех контекстов (тяжёлый LCP)
- Отсутствие lazy loading для невидимых изображений
- Игнорирование WebP (лишние 30-50% трафика)
- Неправильная реализация зума: масштабирование всего изображения вместо фрагмента (смазанный результат)
- Конфликт жестов на мобайле: pinch-to-zoom и свайп переключают изображения одновременно
Сроки и стоимость
| Этап | Срок |
|---|---|
| Базовая галерея (слайдер + тумбнейлы + lazy load) | 3–5 рабочих дней |
| С hover-zoom и lightbox | 1–1.5 недели |
| С pinch-to-zoom, видео и вариантами | 2–3 недели |
| Pipeline генерации превью (с нуля) | +1 неделя |
Стоимость рассчитывается индивидуально. Свяжитесь с нами для оценки вашего проекта — мы подготовим коммерческое предложение с учётом вашего стека и требований.







