Setting Up 360-Photo Viewing in 1C-Bitrix
Buyer wants to examine a product from all sides before ordering. A regular 8-photo gallery doesn't replace the ability to rotate the object. 360-viewing is a sequence of frames (24-72 photos) that create rotation illusion when dragged.
Storing 360-Shot Frames
360-frames are stored like regular images linked to product via iblock property type "File" (F) with "Multiple" flag. Property created with code like IMAGES_360.
When frames added via admin, saved in b_iblock_element_property with PROPERTY_TYPE = 'F' — each frame separate row with common IBLOCK_ELEMENT_ID and IBLOCK_PROPERTY_ID.
Getting frames for frontend:
$element = \CIBlockElement::GetByID($productId)->GetNextElement();
$props = $element->GetProperties();
$frames = $props['IMAGES_360']['VALUE'];
// $frames — array of file IDs from b_file
$frameUrls = array_map(function($fileId) {
return \CFile::GetPath($fileId);
}, (array)$frames);
// Pass to JSON for JS-player
echo json_encode(['frames' => $frameUrls]);
JavaScript Player for 360
Simplest implementation — Canvas or CSS background-position. three-sixty.js or js-cloudimage-360-view libraries suit production.
CSS-sprite implementation (all frames glued into one horizontal sprite):
class Viewer360 {
constructor(el, frames) {
this.el = el;
this.frames = frames;
this.total = frames.length;
this.current = 0;
this.dragging = false;
this.startX = 0;
this.img = new Image();
this.img.src = frames[0];
el.appendChild(this.img);
el.addEventListener('mousedown', e => { this.dragging = true; this.startX = e.clientX; });
document.addEventListener('mouseup', () => { this.dragging = false; });
document.addEventListener('mousemove', e => this.onMove(e));
// Touch
el.addEventListener('touchstart', e => { this.dragging = true; this.startX = e.touches[0].clientX; });
document.addEventListener('touchend', () => { this.dragging = false; });
document.addEventListener('touchmove', e => this.onMove(e.touches[0]));
}
onMove(e) {
if (!this.dragging) return;
const delta = e.clientX - this.startX;
if (Math.abs(delta) > 5) {
this.current = (this.current + (delta > 0 ? 1 : -1) + this.total) % this.total;
this.img.src = this.frames[this.current];
this.startX = e.clientX;
}
}
}
Initialization in bitrix:catalog.element template:
new Viewer360(
document.getElementById('viewer-360'),
<?= json_encode($frameUrls) ?>
);
Frame Preloading
Lazy-loaded frames lag on first rotation. Correct approach — preload all frames in background after page load:
function preloadFrames(urls) {
return Promise.all(urls.map(url => {
return new Promise(resolve => {
const img = new Image();
img.onload = resolve;
img.src = url;
});
}));
}
preloadFrames(frameUrls).then(() => {
// Unlock control after loading
document.getElementById('viewer-360').classList.remove('loading');
});
Container is blocked with spinner until preload completes.
Optimization: WebP and Frame Size
36 frames × 800×600px in JPEG 80% = ~3-5 MB total. For mobile networks this is heavy. Optimization:
- Convert to WebP via
cwebpor Bitrix resize component withwebpformat. - Reduce frame resolution to 600×450 — sufficient for 360-rotation.
- Progressive load: show first frame, load rest in background.
Bitrix generates WebP via \CFile::ResizeImageGet() with 'format' => 'webp' parameter in certain versions. Older versions use external converter via exec('cwebp ...') on upload.
Storing in Separate Iblock
For catalogs with hundreds of products with 360-sets, storing frames in main iblock property creates many b_iblock_element_property records. Alternative — separate iblock for 360-galleries linked to product via PROPERTY_TYPE = 'E' (element link). Simplifies set management and allows linking one set to multiple SKUs.







