Setting Up Product Image Gallery (Swiper/Slider) in 1C-Bitrix
Standard catalog.element template in Bitrix renders additional product images (MORE_PHOTO) as set of <img> tags without gallery logic. Store connects Swiper, gallery works on homepage, but in product card slider initializes before component outputs HTML — gets empty container. Classic Bitrix script execution order problem.
Image Data in catalog.element Component
Component collects all product images in $arResult['MORE_PHOTO'] — array of paths to already-resized images. Originals stored in b_iblock_element.DETAIL_PICTURE and MORE_PHOTO property (type F, multiple). Slider typically needs two sizes: thumbnail for preview strip and full-size for main slide.
In component template template.php:
<?php
$slides = [];
foreach ($arResult['PROPERTIES']['MORE_PHOTO']['FILE_VALUE'] as $fileArr) {
$thumb = \CFile::ResizeImageGet($fileArr['ID'], ['width' => 80, 'height' => 80], BX_RESIZE_IMAGE_PROPORTIONAL);
$full = \CFile::ResizeImageGet($fileArr['ID'], ['width' => 800, 'height' => 800], BX_RESIZE_IMAGE_PROPORTIONAL);
$slides[] = [
'thumb' => $thumb['src'],
'full' => $full['src'],
'alt' => htmlspecialcharsEx($fileArr['DESCRIPTION'] ?: $arResult['NAME']),
];
}
?>
Markup for Swiper
Swiper expects strict structure .swiper > .swiper-wrapper > .swiper-slide. Any deviation — library won't find slides. Main slider and thumbnail slider — two separate Swiper instances linked via thumbs.swiper parameter:
<div class="swiper product-main-swiper" id="productMainSwiper">
<div class="swiper-wrapper">
<?php foreach ($slides as $slide): ?>
<div class="swiper-slide">
<img src="<?= $slide['full'] ?>" alt="<?= $slide['alt'] ?>" loading="lazy">
</div>
<?php endforeach; ?>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<div class="swiper product-thumbs-swiper" id="productThumbsSwiper">
<div class="swiper-wrapper">
<?php foreach ($slides as $slide): ?>
<div class="swiper-slide">
<img src="<?= $slide['thumb'] ?>" alt="" loading="lazy">
</div>
<?php endforeach; ?>
</div>
</div>
Initialization and Script Order
Key Bitrix problem: $APPLICATION->AddHeadScript() adds scripts to <head>, component renders later in <body>. Swiper connected in head, but new Swiper() called in template inline-script — DOM not ready.
Correct solution — deferred initialization via DOMContentLoaded or via prolog/epilog. In component template:
document.addEventListener('DOMContentLoaded', function () {
const thumbsSwiper = new Swiper('#productThumbsSwiper', {
slidesPerView: 4,
spaceBetween: 8,
watchSlidesProgress: true,
});
new Swiper('#productMainSwiper', {
spaceBetween: 0,
thumbs: { swiper: thumbsSwiper },
keyboard: { enabled: true },
});
});
Lazy Loading and LCP
loading="lazy" on first slide kills LCP — browser delays loading main product image. First slide must load without lazy:
foreach ($slides as $i => $slide):
$loading = $i === 0 ? 'eager' : 'lazy';
Also add fetchpriority="high" to first slide — hints browser to raise request priority.
Image Changes on SKU Selection
When user selects trade offer, Bitrix via AJAX updates price and availability block. Gallery doesn't change — tied to parent element. To sync: listen to onSaleComponentOfferSelect event (Bitrix standard event), get offerId, request offer photos via custom AJAX action, recreate slider with new data via swiper.destroy() + reinitialization.







