Setting up a product image gallery (swiper/slider) in 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    565
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

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.