Carrusel para el item de producto en listados

En este tutorial vamos a implementar el comportamiento para que en los listados de productos, se muestre un carrusel de imágenes en cada item de producto

Este carrusel mostrará hasta máximo 10 imágenes por productos y al llegar a la última se mostrará un mensaje de cuantas fotos restan sin ver.

HTML

1. Lo primero que vamos a hacer, es reemplazar la imagen por el componente privado de imagen para el item de producto product-item-image que incluye la segunda imagen que necesitamos.

Vamos a buscar el snipplet item.tpl dentro de la carpeta snipplets/grid, puede ser que en tu diseño este snipplet se llame single_product.tpl

Dentro de este archivo vamos buscar el siguiente código relacionado a la imagen y su contenedor:

<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>

Y lo reemplazamos por el componente privado (en caso de ya tener la funcionalidad de Segunda imagen en rollover debemos tener cuidado de no remover los parámetros del componente privado usados para eso):

{# 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,
    })
}}

El componente privado product-item-image incluye el contenedor de la imagen y las imágenes necesarias (tanto la principal como la secundaria). Su vez también hace uso de otro componente privado específico para la imagen, recomendamos revisar la documentación de cada componente para entender que parámetros aceptan:

2. Una vez incluído el componente de product-item-image, necesitamos agregar dentro de este componente todos los elementos que se encuentran flotando sobre la imagen como por ejemplo las etiquetas.

Vamos a buscar este contenido y lo vamos a en globar en un {% set floating_elements %} de la siguiente manera:

{% 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 %}

Una vez definido "floating_elements", vamos a usarlo en el parámetro "custom_content" del product-item-image de la siguiente 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:

Tener agregados en tu diseño las clases helpers. Podés seguir este este pequeño tutorial para hacerlo (simplemente es copiar y pegar algunas clases, no toma más de 1 minuto).

1.Agregar los estilos dentro del archivo static/style-critical.tpl 

Si en tu diseño usas una hoja de estilos para el CSS crítico, vamos a necesitar el siguiente código dentro de la misma.

{# /* // 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. Necesitamos correr el JS necesario para generar los sliders a medida que cada item de producto es visualizado en la pantalla. Para eso necesitamos sumar el siguiente 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 %}

En el ejemplo podemos ver todos los parámetros que se pueden usar:

  • pagination_type: Por defecto usa "bullets" pero si es necesario usar el formato de fracción (Ej: 1/6), se puede usar el valor "fraction", de lo contrario no es necesario aclararlo.
  • onInit: Sirve para agregar JS luego de que cada slider se inicializa
  • onSlideChange: Sirve para agregar JS luego de que cada slider cambia de slide

2. En caso de tener la funcionalidad de scroll infinito, es necesario agregar el siguiente 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 %}

3. Como en este tutorial usamos la técnica de lazy load con el plugin Lazysizes, necesitamos agregarlo. Para ver como hacerlo podés leer este corto artículo.

Por otro lado también usamos un slider con Swiper, necesitamos agregar el plugin. Para ver cómo hacerlo podés leer este otro artículo

Configuraciones

En el archivo config/settings.txt vamos a agregar el checkbox que activa la funcionalidad dentro de la sección “Listado de productos”.

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>

Traducciones

En este paso agregamos los textos para las traducciones en el archivo 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>"

Activación

Por último podés activar la funcionalidad desde el Administrador nube, en la sección de Personalizar tu diseño actual dentro de Listado de productos:

Recordá, que para que funcione el producto tiene que tener por lo menos 2 imágenes.