Розробка 360°-прегляду товару для інтернет-магазину
360°-перегляд — послідовність фотографій об'єкту, зроблених з рівномірним кроком по колу. Користувач перетягує зображення влево-вправо та бачить товар зі всіх сторін. Технічно це не відео та не 3D — просто анімація через масив статичних кадрів, але ефект створює ілюзію інтерактивного вращення.
Фотозйомка для 360°
Результат залежить від зйомки, а не від коду. Технічні вимоги:
- Кількість кадрів: 24–72. 24 кадри = крок 15° (достатньо), 36 кадрів = крок 10° (плавно), 72 кадри = крок 5° (дуже плавно, але ~3x більше даних)
- Поворотний стіл з рівномірним кроком (motorized turntable) — ключове обладнання
- Освітлення: постійне, без рухомих тіней між кадрами
- Фон: білий або прозорий (PNG з альфою), щоб вбудувати в будь-який дизайн
- Роздільна здатність: 1000–2000px — баланс якості та ваги
Типовий обсяг: 36 кадрів × 200KB = 7MB на один товар. Це багато для сторінки. Вирішується прогресивною загрузкою.
Формати зберігання кадрів
Три варіанти:
Окремі файли: /360/product-42/frame-{01..36}.webp. Просто, прозоро для кеша CDN. При зміні кадру — браузер завантажує потрібний файл (або бере з кеша).
Спрайт (sprite sheet): всі кадри в одному зображенні (6×6 grid для 36 кадрів). Менше HTTP-запитів, але один великий файл (36 × 200KB = 7.2MB). Відображення через background-position. Підходить, якщо всі кадри потрібні одразу.
Відеофайл: кадри конвертуються в MP4 без звука, воспроизводяться через <video> з управлінням через currentTime. Найбільш компактний варіант (H.264 компресія). Управління:
const video = document.querySelector('video');
let isDragging = false;
canvas.addEventListener('mousedown', e => {
isDragging = true;
startX = e.clientX;
startTime = video.currentTime;
});
canvas.addEventListener('mousemove', e => {
if (!isDragging) return;
const delta = (e.clientX - startX) / canvas.offsetWidth;
video.currentTime = Math.max(0, Math.min(
video.duration,
startTime - delta * video.duration
));
});
Реалізація на окремих кадрах
Найбільш поширений підхід — масив зображень:
class Product360Viewer {
private frames: HTMLImageElement[] = [];
private currentFrame = 0;
private isDragging = false;
constructor(
private container: HTMLElement,
private canvas: HTMLCanvasElement,
private frameUrls: string[]
) {
this.preloadFrames();
this.bindEvents();
}
private preloadFrames() {
const loadFrame = (index: number) => {
const img = new Image();
img.src = this.frameUrls[index];
img.onload = () => {
this.frames[index] = img;
if (index === 0) this.render(0);
if (index < this.frameUrls.length - 1) loadFrame(index + 1);
};
};
loadFrame(0);
}
private render(frameIndex: number) {
const ctx = this.canvas.getContext('2d')!;
const img = this.frames[frameIndex];
if (!img) return;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height);
}
private handleDrag(deltaX: number) {
const sensitivity = 3; // пікселів на кадр
const frameDelta = Math.round((this.startX - deltaX) / sensitivity);
this.currentFrame = ((this.startFrame + frameDelta) % this.frameUrls.length + this.frameUrls.length) % this.frameUrls.length;
this.render(this.currentFrame);
}
}
Прогресивна загрузка
7MB одразу при відкритті сторінки — неприйнятно. Стратегія:
- Відображаємо статичне зображення (перший кадр) — він уже в галереї товару
- При наведенні / при попаданні в viewport (IntersectionObserver) — починаємо загрузку кадрів
- Показуємо індикатор загрузки «Загрузка 360°: 45%»
- При загрузці >50% кадрів — активуємо інтерактивність
- Продовжуємо завантажувати решту в фоні
Пріоритет загрузки: непарні кадри (0, 2, 4, 8, 16, 32...) — спочатку грубоа інтерактивність, потім заповнення пропусків.
Touch-события для мобайлу
private bindEvents() {
// Mouse
this.canvas.addEventListener('mousedown', e => this.startDrag(e.clientX));
window.addEventListener('mousemove', e => { if (this.isDragging) this.handleDrag(e.clientX); });
window.addEventListener('mouseup', () => this.isDragging = false);
// Touch
this.canvas.addEventListener('touchstart', e => {
e.preventDefault();
this.startDrag(e.touches[0].clientX);
}, { passive: false });
this.canvas.addEventListener('touchmove', e => {
e.preventDefault();
if (this.isDragging) this.handleDrag(e.touches[0].clientX);
}, { passive: false });
this.canvas.addEventListener('touchend', () => this.isDragging = false);
}
Важливо: passive: false та e.preventDefault() на touchmove — інакше браузер буде скроллити сторінку замість вращення товару.
Готові бібліотеки
Якщо завдання розова та немає спеціальних вимог:
- 360-image-viewer (npm) — легка, vanilla JS, підтримка touch
- Pannellum — для панорам (equirectangular), не для предметної зйомки
- Three.js з equirectangular texture — для справжньої сферичної панорами
Для React: react-360-image-viewer — обертає базову функціональність, але кастомізація обмежена.
Автозапуск вращення
При першому попаданні в viewport — автоматично прокрутити один оборот, потім зупинитися. Це демонструє можливість та підказує користувачу, що зображення інтерактивне.
autoSpin(rotations = 1, fps = 30) {
const totalFrames = this.frameUrls.length * rotations;
let frame = 0;
const interval = setInterval(() => {
this.currentFrame = (this.currentFrame + 1) % this.frameUrls.length;
this.render(this.currentFrame);
if (++frame >= totalFrames) clearInterval(interval);
}, 1000 / fps);
}
Підказки для користувача
Інтерактивність повинна бути очевидною. Стандартні рішення:
- Іконка «360°» поверх зображення в галереї (на тумбнейлі)
- Overlay з підказкою «Перетащите для вращения» — зникає при першому взаємодії
- Стрілочки влево-вправо по бокам (альтернативне управління)
- Кнопки «Пауза/Воспроизвести» для автовращения
Інтеграція з галереєю
360°-перегляд — один з слотів галереї товара. Тумбнейл — спеціальна іконка, не фото. При виборі цього слоту — запускається 360°-віджет замість звичайного зображення. При переході на інший слот — віджет демонтується та освобождає пам'ять (скасовуємо всі незавершені загрузки через AbortController).
Терміни
- Інтеграція готової бібліотеки (react-360-image-viewer або аналог): 3–5 робочих днів
- Кастомна реалізація (Canvas API, прогресивна загрузка, touch): 1.5–2.5 тижні
- Настройка pipeline зйомки та конвертації (якщо немає): додаткова завдання поза розробкою
- Пакетна конвертація існуючих кадрів (WebP, оптимізація): 2–3 дні
Вартість зйомки та підготовки контенту часто перевищує вартість розробки віджету. Це потрібно враховувати при плануванні: 36 кадрів для 500 товарів — це операційна завдача, а не технічна.







