Skip to content

Product Gallery

Use product_gallery module to insert a product gallery on your product template view. This module is available only within a product card context. It does provide configurable gallery with thumbnails view.

Configuration parameters

hasMobileArrowsDisplayEnabled

int if set to 1 the view of navigation arrows for thumbnail gallery below 768px will be enabled.

hasDesktopArrowsDisplayEnabled

int if set to 1 the view of navigation arrows for thumbnail gallery above 768px will be enabled.

thumbnailsPosition

left | right | bottom Sets where the thumbnails gallery is displayed. On the left side of main photo, right side of main photo or below main photo.

displayPhotoDescription

int if set to 1 the description or the name of the image file will be rendered.

Module source code

{% from "@macros/image.twig" import image %}
{% from "@macros/slider_arrow_left.twig" import slider_arrow_left %}
{% from "@macros/slider_arrow_right.twig" import slider_arrow_right %}
{% from "@macros/icon.twig" import icon %}

{% set product = ObjectApi.getProduct(product_id) %}
{% set shopUrls = ObjectApi.getShopUrls() %}

{% set availability = product.availability %}

{% set isInactive = not availability.isAvailable and availability.visibilityConfig.isProductPageGalleryAndNameGrey %}
{% set inactiveMainImageClass = isInactive ? 'product-gallery__main-photo_inactive inactive' : '' %}
{% set inactiveThumbnailClass = isInactive ? 'product-gallery__thumbnail-item_inactive inactive' : '' %}

{% set featuredImage = product.featuredImage %}

{% set isThumbnailsPositionUnder = moduleConfig.thumbnailsPosition == 'under' %}

{% set mainPhotoGallerySettings = {
    "type": "slide",
    "rewind": true,
    "pagination": false,
    "arrows" :true,
    "keyboard": "global",
    "lazyLoad": "nearby",
    "gap": "0.5rem",
    "breakpoints": {
        768: {
            "padding": { right: moduleConfig.hasMobileArrowsDisplayEnabled ? "" : "2rem" } 
        }
    }
} %}

{% if isThumbnailsPositionUnder %}
    {% set thumbnailsGallerySettings = {
        "arrows": false,
        "fixedWidth": 60,
        "fixedHeight": 80,
        "perMove": 1,
        "pagination": false,
        "isNavigation": true,
        "gap": "0.5rem",
        "focus": 'center'
    } %}
{% else %}
    {% set thumbnailsGallerySettings = {
        "arrows": false,
        "fixedWidth": 60,
        "fixedHeight": 80,
        "direction": 'ttb',
        "autoHeight": true,
        "height": 'auto',
        "pagination": false,
        "isNavigation": true,
        "lazyLoad": true,
        "focus": 'center'
    } %}
{% endif %}

<product-gallery
        on-interaction
        main="product-main-photo-{{ moduleInstance }}"
        thumbnails="product-thumbnails-{{ moduleInstance }}"
        class="product-gallery{% if isThumbnailsPositionUnder %} product-gallery_vertical{% endif %}"
>
    <h-slider
            id="product-main-photo-{{ moduleInstance }}"
            class="splide product-gallery__slider product-gallery__slider_main-image"
            settings="{{ mainPhotoGallerySettings | json_encode }}">
        <div class="splide__track">
            <ul class="splide__list">
                {{ product.images.setItemCountPerPage(product.images.count) }}

                {% for image in product.images %}
                    <li class="splide__slide{% if not image.isFeatured %} splide__slide_hide{% endif %}">
                        <a class="js__gallery-anchor-image" href="{{ image.webpUrl }}">
                            {% set imgProperties = {
                                src: image.webpThumbnailUrl(systemConfig.xlSize, systemConfig.xlSize),
                                class: "js__open-gallery product-gallery__main-image #{inactiveMainImageClass}",
                                alt: image.name,
                                title: image.name,
                                width: systemConfig.xlSize,
                                height: systemConfig.xlSize,
                                sizes: "(min-width: 576px) #{systemConfig.xlSize}px, 100vw",
                                "data-gallery-name-to-open": "productGallery",
                                "data-gallery-name": "productGallery",
                                "data-image-description": moduleConfig.displayPhotoDescription ? image.name : '',
                            } %}

                            {% if image.isFeatured %}
                                {% set imgProperties = imgProperties|merge({ 'srcset': image.webpThumbnailUrl(150,150)  ~' 150w, ' ~ image.webpThumbnailUrl(500,500)  ~ ' 500w, ' ~  image.webpUrl ~ ' 800w', 'fetchpriority': 'high', 'decoding': 'sync'}) %}
                            {% else %}
                                {% set imgProperties = imgProperties|merge({ 'data-splide-lazy-srcset': image.webpThumbnailUrl(150,150)  ~' 150w, ' ~ image.webpThumbnailUrl(500,500)  ~ ' 500w, ' ~  image.webpUrl ~ ' 800w', decoding: 'async', loading: 'lazy' }) %}
                            {% endif %}

                            {{ image({ img: imgProperties }, [
                                {
                                    src: image.webpThumbnailUrl(systemConfig.xlSize, systemConfig.xlSize),
                                    type:  'image/webp'
                                }
                            ]) }}
                        </a>
                    </li>
                {% endfor %}
            </ul>
        </div>

        <div class="splide__arrows" hidden>
            {{ slider_arrow_left({
                alignment: 'pagination',
                title: translate('Previous image'),
                class: (moduleConfig.hasMobileArrowsDisplayEnabled ? '' : ' hidden-xs-only hidden-sm-only') ~ (moduleConfig.hasDesktopArrowsDisplayEnabled ? '' : ' hidden-md-only hidden-lg-only hidden-xl-only hidden-xxl-only hidden-xxxl-only')
            }) }}

            {{ slider_arrow_right({
                alignment: 'pagination',
                title: translate('Next image'),
                class: (moduleConfig.hasMobileArrowsDisplayEnabled ? '' : ' hidden-xs-only hidden-sm-only') ~ (moduleConfig.hasDesktopArrowsDisplayEnabled ? '' : ' hidden-md-only hidden-lg-only hidden-xl-only hidden-xxl-only hidden-xxxl-only')
            }) }}
        </div>
    </h-slider>

    {% if product.images.count > 1 %}
        <h-slider
            id="product-thumbnails-{{ moduleInstance }}"
            class="splide splide_non-floating-arrows-{% if isThumbnailsPositionUnder %}horizontal{% else %}vertical{% endif %} hidden-xs-only product-gallery__slider product-gallery__slider_thumbnails product-gallery__slider_thumbnails-{{ moduleConfig.thumbnailsPosition }}"
            settings="{{ thumbnailsGallerySettings | json_encode }}">

            {% if isThumbnailsPositionUnder %}
                <div class="product-gallery__thumbnail-arrow product-gallery__thumbnail-arrow_left js__splide-move-by-prev" hidden>
                    <button class="slider__arrow_secondary " >
                        {{ icon('icon-chevron-left', {
                            size: 'm',
                            title: 'todo: title'
                        }) }}
                    </button>
                </div>
            {% else %}
                <div class="product-gallery__thumbnail-arrow product-gallery__thumbnail-arrow_up js__splide-move-by-prev" hidden>
                    <button class="slider__arrow_secondary">
                        {{ icon('icon-chevron-up', {
                            size: 'm',
                            title: 'todo: title'
                        }) }}
                    </button>
                </div>
            {% endif %}

            <div class="splide__track {% if isThumbnailsPositionUnder %} splide__track_horizontal {% endif %}">
                <ul class="splide__list {% if isThumbnailsPositionUnder %} splide__list_horizontal {% endif %}">
                    {% for image in product.images %}
                        <li class="splide__slide">
                            <div class="product-gallery__thumbnail-item">
                                {{ image({
                                    img: {
                                        src: image.thumbnailUrl(systemConfig.xsSize, systemConfig.xsSize),
                                        alt: image.name,
                                        title: image.name,
                                        width: systemConfig.xsSize,
                                        height: systemConfig.xsSize,
                                        decoding: 'async',
                                        loading: 'lazy',
                                        class: inactiveThumbnailClass
                                    }
                                }, [
                                    {
                                        src: image.webpThumbnailUrl(systemConfig.xsSize, systemConfig.xsSize),
                                        type:  'image/webp'
                                    }
                                ]) }}
                            </div>
                        </li>
                    {% endfor %}
                </ul>
            </div>

            {% if isThumbnailsPositionUnder %}
                <div class="product-gallery__thumbnail-arrow product-gallery__thumbnail-arrow_right js__splide-move-by-next" hidden>
                    <button class="slider__arrow_secondary">
                        {{ icon('icon-chevron-right', {
                            size: 'm',
                            title: 'todo: title'
                        }) }}
                    </button>
                </div>
            {% else %}
                <div class="product-gallery__thumbnail-arrow product-gallery__thumbnail-arrow_down js__splide-move-by-next" hidden>
                    <button class="slider__arrow_secondary">
                        {{ icon('icon-chevron-down', {
                            size: 'm',
                            title: 'todo: title'
                        }) }}
                    </button>
                </div>
            {% endif %}
        </h-slider>
    {% endif %}
</product-gallery>

{% set productImages = []|merge(product.images
    | filter(image => image.isPlaceholder == false)
    | sort((a,b) => b.isFeatured - a.isFeatured)
    | map(image => "#{image.url.absolute}")) %}

{% set productGalleryJsonLd = {
    "@context": [
        "http://schema.org/",
        { "@base": "#{shopUrls.mainPageUrl.absolute}" }
    ],
    "@id": "#{product.url.relative}",
    "image": productImages
} %}

<script type="application/ld+json">{{ productGalleryJsonLd | json_encode | raw }}</script>

The product gallery consists of two parts which are h-slider webcomponents. Each differently configured but both synced with each other with help of product-gallery webcomponent that serves as a parent for them.

As seen in source code above there is a lot of variables that may be changed, resulting in different gallery behaviour. Both galleries (main and thumbnail) use h-slider webcomponent where You can read about possible configuration.

The module uses JSON-LD and Microdata from schema.org to optimize search results in browsers.

Webcomponents reference

Macros reference

Used Object Api methods

Used styles

Module configuration schema

[
  {
    "state": "unfolded",
    "label": "Gallery main view",
    "elements": [
      {
        "type": "header",
        "name": "header_mobile_devices",
        "label": "Mobile devices",
        "options": {
          "icon": "mobile_devices"
        },
        "children": [
          {
            "type": "checkbox",
            "name": "hasMobileArrowsDisplayEnabled",
            "label": "Enable navigation arrows",
            "defaultValue": 1
          }
        ]
      },
      {
        "type": "header",
        "name": "header_computers",
        "label": "Computers",
        "options": {
          "icon": "computers"
        },
        "children": [
          {
            "type": "checkbox",
            "name": "hasDesktopArrowsDisplayEnabled",
            "label": "Enable navigation arrows",
            "defaultValue": 1
          },
          {
            "type": "radio",
            "name": "thumbnailsPosition",
            "label": "Thumbnail display position",
            "options": {
              "radioOptions": [
                {
                  "key": "left",
                  "label": "On the left of the photo"
                },
                {
                  "key": "right",
                  "label": "On the right of the photo"
                },
                {
                  "key": "under",
                  "label": "Under the photo"
                }
              ]
            }
          }
        ]
      }
    ]
  },
  {
    "state": "unfolded",
    "label": "Full screen view",
    "elements": [
      {
        "type": "infobox",
        "name": "infobox",
        "options": {
          "type": "warning",
          "message": "Changes to the settings in this section will not be visible in the live view. To see the changes, save them, click \"Preview\" and click on the photo in the gallery."
        }
      },
      {
        "type": "checkbox",
        "name": "displayPhotoDescription",
        "label": "Display photo description \/ file name",
        "defaultValue": 1
      }
    ]
  }
]