Carrossel para itens de produtos em listagens

Neste tutorial, vamos implementar o comportamento para que, nas listagens de produtos, seja exibido um carrossel de imagens em cada item de produto.

HTML

1. A primeira coisa que faremos é substituir a imagem pelo componente privado de imagem para o item de produto product-item-image que inclui a segunda imagem que precisamos.

Vamos procurar o snipplet item.tpl dentro da pasta snipplets/grid. Pode ser que, no seu layout, este snipplet se chame single_product.tpl.

Dentro deste arquivo, vamos procurar o seguinte código relacionado à imagem e seu contêiner:

<div class="item-image mb-2">
    <div style="padding-bottom: {{ item_img_spacing }}%;" class="p-relative" data-store="product-item-image-{{ product.id }}">
        <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}">
            <img alt="{{ item_img_alt }}" data-expand="-10" src="{{ 'images/empty-placeholder.png' | static_url }}" data-srcset="{{ item_img_srcset | product_image_url('small')}} 240w, {{ item_img_srcset | product_image_url('medium')}} 320w, {{ item_img_srcset | product_image_url('large')}} 480w" class="js-item-image lazyload img-absolute img-absolute-centered fade-in" width="{{ item_img_width }}" height="{{ item_img_height }}" /> 
            <div class="placeholder-fade"></div>
        </a>
    </div>
</div>

E o substituímos pelo componente privado (se você já tiver a funcionalidade Segunda imagem em rollagem, precisamos ter cuidado para não remover os parâmetros do componente privado usados para isso.):

{# Item image slider #}

{% set show_image_slider = 
    (template == 'category' or template == 'search')
    and settings.product_item_slider 
    and not reduced_item
    and not slide_item
    and not has_filters
    and product.other_images
%}

{% if show_image_slider %}
    {% set slider_controls_container_class = 'item-slider-controls-container d-none d-md-block' %}
    {% set slider_control_class = 'icon-inline icon-w-8 icon-2x svg-icon-text' %}
    {% set control_prev = include ('snipplets/svg/chevron-left.tpl', {svg_custom_class: slider_control_class}) %}
    {% set control_next = include ('snipplets/svg/chevron-right.tpl', {svg_custom_class: slider_control_class}) %}
{% endif %}

{% set image_classes = 'js-item-image lazyload img-absolute img-absolute-centered fade-in' %}
{% set data_expand = show_image_slider ? '50' : '-10' %}

{{ component(
    'product-item-image', {
        image_lazy: true,
        image_lazy_js: true,
        image_thumbs: ['small', 'medium', 'large', 'huge', 'original'],
        image_data_expand: data_expand,
        slider: show_image_slider,
        placeholder: true,
        svg_sprites: false,
        product_item_image_classes: {
            image_container: 'item-image mb-2',
            image_padding_container: 'p-relative',
            image: image_classes,
            slider_container: 'swiper-container position-absolute h-100 w-100',
            slider_wrapper: 'swiper-wrapper',
            slider_slide: 'swiper-slide item-image-slide',
            slider_control_pagination: 'swiper-pagination item-slider-pagination font-small d-md-none',
            slider_control: 'fa-lg svg-icon-primary',
            slider_control_prev_container: 'swiper-button-prev ' ~ slider_controls_container_class,
            slider_control_next_container: 'swiper-button-next ' ~ slider_controls_container_class,
            more_images_message: 'item-more-images-message font-small',
            placeholder: 'placeholder-fade',
        },
        custom_control_prev: control_prev,
        custom_control_next: control_next,
    })
}}

O componente privado product-item-image inclui o contêiner da imagem e as imagens necessárias (tanto a principal quanto a secundária). Por sua vez, ele também utiliza outro componente privado específico para a imagem. Recomendamos revisar a documentação de cada componente para entender quais parâmetros eles aceitam:

2. Uma vez incluído o componente product-item-image, precisamos adicionar dentro deste componente todos os elementos que estão flutuando sobre a imagem, como, por exemplo, as etiquetas.

Vamos procurar este conteúdo e vamos englobá-lo em um {% set floating_elements %} da seguinte maneira:

{% set floating_elements %}
    {% if not reduced_item %}
        {% if settings.product_color_variants %}
            {% include 'snipplets/labels.tpl' with {color: true} %}
            {% include 'snipplets/grid/item-colors.tpl' %}
        {% else %}
            {% include 'snipplets/labels.tpl' %}
        {% endif %}
    {% endif %}
{% endset %}

Uma vez definido "floating_elements", vamos usá-lo no parâmetro "custom_content" do product-item-image da seguinte forma:

{{ component(
    'product-item-image', {
        image_lazy: true,
        image_lazy_js: true,
        image_thumbs: ['small', 'medium', 'large', 'huge', 'original'],
        image_data_expand: data_expand,
        slider: show_image_slider,
        placeholder: true,
        svg_sprites: false,
        custom_content: floating_elements,
        product_item_image_classes: {
            image_container: 'item-image mb-2',
            image_padding_container: 'p-relative',
            image: image_classes,
            slider_container: 'swiper-container position-absolute h-100 w-100',
            slider_wrapper: 'swiper-wrapper',
            slider_slide: 'swiper-slide item-image-slide',
            slider_control_pagination: 'swiper-pagination item-slider-pagination font-small d-md-none',
            slider_control: 'fa-lg svg-icon-primary',
            slider_control_prev_container: 'swiper-button-prev ' ~ slider_controls_container_class,
            slider_control_next_container: 'swiper-button-next ' ~ slider_controls_container_class,
            more_images_message: 'item-more-images-message font-small',
            placeholder: 'placeholder-fade',
        },
        custom_control_prev: control_prev,
        custom_control_next: control_next,
    })
}}

CSS

Requisito:

Ter adicionado helper classes em seu layout. Você pode seguir este pequeno tutorial para fazer isso (é só copiar e colar algumas classes, não leva mais que 1 minuto).

1.Adicione os estilos no arquivo static/style-critical.tpl

Se em seu layout você usar um stylesheet para o CSS crítico, precisaremos do seguinte código dentro dele.

{# /* // Grid item */ #}

.item-image-slide img{
  max-width: 100%;
  object-fit: contain;
  object-position: top;
}
.item-more-images-message {
  position: absolute;
  top: 10px;
  right: 15px;
  z-index: 1;
  opacity: 0;
  text-transform: uppercase;
  transform: initial;
  transition: all 0.2s ease;
}
@media (min-width: 768px) { 
  .item-slider-controls-container {
    opacity: 0;
    transition: opacity .2s ease;
  }
  .item-slider-controls-container.swiper-button-disabled {
    opacity: 0;
    cursor: auto;
  }
  .item-image:hover .item-slider-controls-container:not(.swiper-button-disabled) {
    opacity: 1;
  }
}

JS

1. Precisamos rodar o JS necessário para gerar os sliders à medida que cada item de produto é visualizado na tela. Para isso, precisamos adicionar o seguinte código:

{% set has_item_slider = settings.product_item_slider %}

{% if template == 'category' or template == 'search' %}

    {# /* // Product item slider */ #}

    {% if has_item_slider %}

        LS.productItemSlider({ 
            pagination_type: 'fraction',
            onInit: function(){
                console.log('init');
            },
            onSlideChange: function(){
                console.log('slideChange');
            }
        });

    {% endif %}
{% endif %}

No exemplo, podemos ver todos os parâmetros que podem ser usados:

  • pagination_type: Por padrão, usa "bullets", mas se for necessário usar o formato de fração (Ex: 1/6), pode-se usar o valor "fraction". Caso contrário, não é necessário especificá-lo.
  • onInit: Serve para adicionar JS depois que cada slider é inicializado.
  • onSlideChange: Serve para adicionar JS depois que cada slider muda de slide.

2. Caso tenha a funcionalidade de scroll infinito, é necessário adicionar o seguinte código:

{# /* // Infinite scroll */ #}

{% if pages.current == 1 and not pages.is_last %}

    LS.hybridScroll({
        productGridSelector: '.js-product-table',
        spinnerSelector: '#js-infinite-scroll-spinner',
        loadMoreButtonSelector: '.js-load-more',
        hideWhileScrollingSelector: ".js-hide-footer-while-scrolling",
        productsBeforeLoadMoreButton: 50,
        productsPerPage: 12,
        {% if has_item_slider %}
            afterLoaded: function(){
                LS.productItemSlider({ 
                    pagination_type: 'fraction',
                });
            },
        {% endif %}
    });

{% endif %}

Como neste tutorial usamos a técnica de lazy load com o plugin Lazysizes, precisamos adicioná-lo. Para ver como fazer isso, você pode ler este pequeno artigo.

Por outro lado também usamos um slider com o Swiper, precisamos adicionar o plugin. Para ver como fazer isso, você pode ler este pequeno artigo.

Configurações

No arquivo config/settings.txt, adicionaremos um checkbox que ativa a funcionalidade na seção "Lista de produtos".

title
    title = Fotos del producto
checkbox
    name = product_item_slider
    description = Mostrar las fotos en un carrusel para cada producto
subtitle
    subtitle = <span class='js-description-html legend d-block p-top-half'>El carrusel aplica sólo a listados de categorías y resultados de búsqueda</span>

Traduções

Nesta etapa, adicionamos os textos para as traduções no arquivo config/translations.txt.

es "Fotos del producto"
pt "Fotos do produto"
en "Product photos"
es_mx "Fotos del producto"

es "Mostrar las fotos en un carrusel para cada producto"
pt "Exibir fotos em um carrossel para cada produto"
en "Show photos in a carousel for each product"
es_mx "Mostrar las fotos en un carrusel para cada producto"

es "<span class='js-description-html legend d-block p-top-half'>El carrusel aplica sólo a listados de categorías y resultados de búsqueda</span>"
pt "<span class='js-description-html legend d-block p-top-half'>O carrossel se aplica somente a listagens de categorias e resultados de pesquisa</span>"
es_mx "<span class='js-description-html legend d-block p-top-half'>El carrusel aplica sólo a listados de categorías y resultados de búsqueda</span>"

Ativação

Por último, você pode ativar funcionalidade no Administrador Nuvem, na seção ‘Personalizar seu layout atual’ dentro de ‘Lista de produtos’:

Lembre-se, para que o produto funcione, ele deve ter pelo menos 2 imagens.