I have several request to make a vertical slider for Dawn 2.5 and below. If you have Dawn 2.5 and above, please refer to this link instead.
You can reposition the slider from left to right and vice versa.
Check DEMO store here 💻. Password: made4uo
To start:
1. You have to go to your Admin store > Themes > Actions > Edit code.
2. Open the Sections folder, and look for "main-product.liquid".
3. Replace the existing code with the code below.
{% comment %}theme-check-disable TemplateLength{% endcomment %} {{ 'section-main-product.css' | asset_url | stylesheet_tag }} {{ 'component-accordion.css' | asset_url | stylesheet_tag }} {{ 'component-price.css' | asset_url | stylesheet_tag }} {{ 'component-rte.css' | asset_url | stylesheet_tag }} {{ 'product-slider.css' | asset_url | stylesheet_tag }} {{ 'component-rating.css' | asset_url | stylesheet_tag }} {{ 'component-loading-overlay.css' | asset_url | stylesheet_tag }} <link rel="stylesheet" href="{{ 'component-deferred-media.css' | asset_url }}" media="print" onload="this.media='all'"> <script src="{{ 'product-form.js' | asset_url }}" defer="defer"></script> {%- assign first_3d_model = product.media | where: "media_type", "model" | first -%} {%- if first_3d_model -%} {{ 'component-product-model.css' | asset_url | stylesheet_tag }} <link id="ModelViewerStyle" rel="stylesheet" href="https://cdn.shopify.com/shopifycloud/model-viewer-ui/assets/v1.0/model-viewer-ui.css" media="print" onload="this.media='all'"> <link id="ModelViewerOverride" rel="stylesheet" href="{{ 'component-model-viewer-ui.css' | asset_url }}" media="print" onload="this.media='all'"> {%- endif -%} <section class="page-width custom-width"> <div class="product grid grid--1-col {% if product.media.size > 0 %}grid--2-col-tablet{% else %}product--no-media{% endif %}"> <div class="grid__item product__media-wrapper"> <vertical-slider-component> <div class="slider-container" style="flex-direction: {{section.settings.slider_placement}}" > <a class="skip-to-content-link button visually-hidden" href="#ProductInfo-{{ section.id }}"> {{ "accessibility.skip_to_product_info" | t }} </a> <div class="mainContainer"> <ul class="mainBox"> {%- for media in product.media -%} <li class="mainBoxItem product__media-item grid__item slider__slide {% if media.media_type != 'image' %} hide-modal {% endif %} {% if media.media_type == 'image' %}image-zoom {% endif %}" data-media-id="{{ section.id }}-{{ media.id }}" data-media-index="{{forloop.index}}"> {% render 'product-thumbnail', media: media, position: forloop.index, loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true %} </li> {%- endfor -%} </ul> <div class="slider-buttons no-js-hidden{% if product.media.size < 2 %} small-hide{% endif %}"> <button type="button" class="slider-button button--prev" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}">{% render 'icon-caret' %}</button> <div class="slider-counter caption"> <span class="slider-counter--current">1</span> <span aria-hidden="true"> / </span> <span class="visually-hidden">{{ 'accessibility.of' | t }}</span> <span class="slider-counter--total" data-total="{% if section.settings.hide_variants %}{{ product.media.size | minus: variant_images.size | plus: 1 }}{% else %}{{ product.media.size }}{% endif %}" >{% if section.settings.hide_variants %}{{ product.media.size | minus: variant_images.size | plus: 1 }}{% else %}{{ product.media.size }}{% endif %}</span> </div> <button type="button" class="slider-button button--next" name="next" aria-label="{{ 'accessibility.next_slide' | t }}">{% render 'icon-caret' %}</button> </div> </div> <div class="slider-wrapper thumbContainer"> <ul class="thumbBox slider" role="list"> {%- assign variant_images = product.images| where: 'attached_to_variant?', true | map: 'src' -%} {%- for media in product.media -%} <li class="thumbBoxItem product-slider slider__slide" data-thumb-id="{{ section.id }}-{{ media.id }}" {% if section.settings.hide_variants and variant_images contains media.src %} style="display: none;" {% endif %} > {% if media.media_type == 'video' %} <div class="video-btn" style="display: block" > <span class="product__media-icon motion-reduce v-btn" aria-hidden="true"> {%- liquid case media.media_type when 'video' or 'external_video' render 'icon-play' when 'model' render 'icon-3d-model' else render 'icon-zoom' endcase -%} </span> <img class="slide-image {% if forloop.first %}active-thumb {% endif %}" src="{{ media.preview_image | img_url: 'large' }}" alt="{{ thumbnailAlt }}" {% if section.settings.square_image == true %} style="aspect-ratio: 1;" {% endif %}> </div> {% else %} <img class="slide-image {% if forloop.first %}active-thumb {% endif %}" src="{{ media.preview_image | img_url: 'large' }}" alt="{{ thumbnailAlt }}" {% if section.settings.square_image == true %} style="aspect-ratio: 1;" {% endif %}> {% endif %} </li> {% endfor %} </ul> <div class="button-wrapper {% if product.media.size <= 5 %} hidden {% endif %}" > <button type="button" class="slider-button slider-button--prev bigger-slider" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}">{% render 'icon-caret' %}</button> <button type="button" class="slider-button slider-button--next bigger-slider" name="next" aria-label="{{ 'accessibility.next_slide' | t }}">{% render 'icon-caret' %}</button> </div> </div> </vertical-slider-component> </div> {%- if first_3d_model -%} <button class="button button--full-width product__xr-button" type="button" aria-label="{{ 'products.product.xr_button_label' | t }}" data-shopify-xr data-shopify-model3d-id="{{ first_3d_model.id }}" data-shopify-title="{{ product.title | escape }}" data-shopify-xr-hidden > {% render 'icon-3d-model' %} {{ 'products.product.xr_button' | t }} </button> {%- endif -%} </div> <div class="product__info-wrapper grid__item"> <div id="ProductInfo-{{ section.id }}" class="product__info-container{% if section.settings.enable_sticky_info %} product__info-container--sticky{% endif %}"> {%- assign product_form_id = 'product-form-' | append: section.id -%} {%- for block in section.blocks -%} {%- case block.type -%} {%- when '@app' -%} {% render block %} {%- when 'text' -%} <p class="product__text{% if block.settings.text_style == 'uppercase' %} caption-with-letter-spacing{% elsif block.settings.text_style == 'subtitle' %} subtitle{% endif %}" {{ block.shopify_attributes }}> {{- block.settings.text -}} </p> {%- when 'title' -%} <h1 class="product__title" {{ block.shopify_attributes }}> {{ product.title | escape }} </h1> {%- when 'price' -%} <div class="no-js-hidden" id="price-{{ section.id }}" {{ block.shopify_attributes }}> {%- render 'price', product: product, use_variant: true, show_badges: true, price_class: 'price--large' -%} </div> {%- if shop.taxes_included or shop.shipping_policy.body != blank -%} <div class="product__tax caption rte"> {%- if shop.taxes_included -%} {{ 'products.product.include_taxes' | t }} {%- endif -%} {%- if shop.shipping_policy.body != blank -%} {{ 'products.product.shipping_policy_html' | t: link: shop.shipping_policy.url }} {%- endif -%} </div> {%- endif -%} <div {{ block.shopify_attributes }}> {%- form 'product', product, id: 'product-form-installment', class: 'installment caption-large' -%} <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}"> {{ form | payment_terms }} {%- endform -%} </div> {%- when 'description' -%} {%- if product.description != blank -%} <div class="product__description rte"> {{ product.description }} </div> {%- endif -%} {%- when 'custom_liquid' -%} {{ block.settings.custom_liquid }} {%- when 'collapsible_tab' -%} <div class="product__accordion accordion" {{ block.shopify_attributes }}> <details> <summary> <div class="summary__title"> {% render 'icon-accordion', icon: block.settings.icon %} <h2 class="h4 accordion__title"> {{ block.settings.heading | default: block.settings.page.title }} </h2> </div> {% render 'icon-caret' %} </summary> <div class="accordion__content rte"> {{ block.settings.content }} {{ block.settings.page.content }} </div> </details> </div> {%- when 'quantity_selector' -%} <div class="product-form__input product-form__quantity" {{ block.shopify_attributes }}> <label class="form__label" for="Quantity-{{ section.id }}"> {{ 'products.product.quantity.label' | t }} </label> <quantity-input class="quantity"> <button class="quantity__button no-js-hidden" name="minus" type="button"> <span class="visually-hidden">{{ 'products.product.quantity.decrease' | t: product: product.title | escape }}</span> {% render 'icon-minus' %} </button> <input class="quantity__input" type="number" name="quantity" id="Quantity-{{ section.id }}" min="1" value="1" form="product-form-{{ section.id }}" > <button class="quantity__button no-js-hidden" name="plus" type="button"> <span class="visually-hidden">{{ 'products.product.quantity.increase' | t: product: product.title | escape }}</span> {% render 'icon-plus' %} </button> </quantity-input> </div> {%- when 'popup' -%} <modal-opener class="product-popup-modal__opener no-js-hidden" data-modal="#PopupModal-{{ block.id }}" {{ block.shopify_attributes }}> <button id="ProductPopup-{{ block.id }}" class="product-popup-modal__button link" type="button" aria-haspopup="dialog">{{ block.settings.text | default: block.settings.page.title }}</button> </modal-opener> <a href="{{ block.settings.page.url }}" class="product-popup-modal__button link no-js">{{ block.settings.text }}</a> {%- when 'share' -%} <share-button class="share-button" {{ block.shopify_attributes }}> <button class="share-button__button hidden"> {% render 'icon-share' %} {{ block.settings.share_label | escape }} </button> <details> <summary class="share-button__button"> {% render 'icon-share' %} {{ block.settings.share_label | escape }} </summary> <div id="Product-share-{{ section.id }}" class="share-button__fallback motion-reduce"> <div class="field"> <span id="ShareMessage-{{ section.id }}" class="share-button__message hidden" role="status"> </span> <input type="text" class="field__input" id="url" value="{{ shop.url | append: product.url }}" placeholder="{{ 'general.share.share_url' | t }}" onclick="this.select();" readonly > <label class="field__label" for="url">{{ 'general.share.share_url' | t }}</label> </div> <button class="share-button__close hidden no-js-hidden"> {% render 'icon-close' %} <span class="visually-hidden">{{ 'general.share.close' | t }}</span> </button> <button class="share-button__copy no-js-hidden"> {% render 'icon-clipboard' %} <span class="visually-hidden">{{ 'general.share.copy_to_clipboard' | t }}</span> </button> </div> </details> </share-button> <script src="{{ 'share.js' | asset_url }}" defer="defer"></script> {%- when 'variant_picker' -%} {%- unless product.has_only_default_variant -%} {%- if block.settings.picker_type == 'button' -%} <variant-radios class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}> {%- for option in product.options_with_values -%} <fieldset class="js product-form__input"> <legend class="form__label">{{ option.name }}</legend> {%- for value in option.values -%} <input type="radio" id="{{ section.id }}-{{ option.name }}-{{ forloop.index0 }}" name="{{ option.name }}" value="{{ value | escape }}" form="product-form-{{ section.id }}" {% if option.selected_value == value %}checked{% endif %} > <label for="{{ section.id }}-{{ option.name }}-{{ forloop.index0 }}"> {{ value }} </label> {%- endfor -%} </fieldset> {%- endfor -%} <script type="application/json"> {{ product.variants | json }} </script> </variant-radios> {%- else -%} <variant-selects class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}> {%- for option in product.options_with_values -%} <div class="product-form__input product-form__input--dropdown"> <label class="form__label" for="Option-{{ section.id }}-{{ forloop.index0 }}"> {{ option.name }} </label> <div class="select"> <select id="Option-{{ section.id }}-{{ forloop.index0 }}" class="select__select" name="options[{{ option.name | escape }}]" form="product-form-{{ section.id }}" > {%- for value in option.values -%} <option value="{{ value | escape }}" {% if option.selected_value == value %}selected="selected"{% endif %}> {{ value }} </option> {%- endfor -%} </select> {% render 'icon-caret' %} </div> </div> {%- endfor -%} <script type="application/json"> {{ product.variants | json }} </script> </variant-selects> {%- endif -%} {%- endunless -%} <noscript class="product-form__noscript-wrapper-{{ section.id }}"> <div class="product-form__input{% if product.has_only_default_variant %} hidden{% endif %}"> <label class="form__label" for="Variants-{{ section.id }}">{{ 'products.product.product_variants' | t }}</label> <div class="select"> <select name="id" id="Variants-{{ section.id }}" class="select__select" form="{{ product_form_id }}"> {%- for variant in product.variants -%} <option {% if variant == product.selected_or_first_available_variant %}selected="selected"{% endif %} {% if variant.available == false %}disabled{% endif %} value="{{ variant.id }}" > {{ variant.title }} {%- if variant.available == false %} - {{ 'products.product.sold_out' | t }}{% endif %} - {{ variant.price | money | strip_html }} </option> {%- endfor -%} </select> {% render 'icon-caret' %} </div> </div> </noscript> {%- when 'buy_buttons' -%} <div {{ block.shopify_attributes }}> <product-form class="product-form"> <div class="product-form__error-message-wrapper" role="alert" hidden> <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-error" viewBox="0 0 13 13"> <circle cx="6.5" cy="6.50049" r="5.5" stroke="white" stroke-width="2"/> <circle cx="6.5" cy="6.5" r="5.5" fill="#EB001B" stroke="#EB001B" stroke-width="0.7"/> <path d="M5.87413 3.52832L5.97439 7.57216H7.02713L7.12739 3.52832H5.87413ZM6.50076 9.66091C6.88091 9.66091 7.18169 9.37267 7.18169 9.00504C7.18169 8.63742 6.88091 8.34917 6.50076 8.34917C6.12061 8.34917 5.81982 8.63742 5.81982 9.00504C5.81982 9.37267 6.12061 9.66091 6.50076 9.66091Z" fill="white"/> <path d="M5.87413 3.17832H5.51535L5.52424 3.537L5.6245 7.58083L5.63296 7.92216H5.97439H7.02713H7.36856L7.37702 7.58083L7.47728 3.537L7.48617 3.17832H7.12739H5.87413ZM6.50076 10.0109C7.06121 10.0109 7.5317 9.57872 7.5317 9.00504C7.5317 8.43137 7.06121 7.99918 6.50076 7.99918C5.94031 7.99918 5.46982 8.43137 5.46982 9.00504C5.46982 9.57872 5.94031 10.0109 6.50076 10.0109Z" fill="white" stroke="#EB001B" stroke-width="0.7"/> </svg> <span class="product-form__error-message"></span> </div> {%- form 'product', product, id: product_form_id, class: 'form', data-type: 'add-to-cart-form' -%} <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}" disabled> <div class="product-form__buttons"> <button type="submit" name="add" class="product-form__submit button button--full-width {% if block.settings.show_dynamic_checkout and product.selling_plan_groups == empty %}button--secondary{% else %}button--primary{% endif %}" {% if product.selected_or_first_available_variant.available == false %}disabled{% endif %} > <span> {%- if product.selected_or_first_available_variant.available -%} {{ 'products.product.add_to_cart' | t }} {%- else -%} {{ 'products.product.sold_out' | t }} {%- endif -%} </span> <div class="loading-overlay__spinner hidden"> <svg aria-hidden="true" focusable="false" role="presentation" class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg"> <circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle> </svg> </div> </button> {%- if block.settings.show_dynamic_checkout -%} {{ form | payment_button }} {%- endif -%} </div> {%- endform -%} </product-form> {{ 'component-pickup-availability.css' | asset_url | stylesheet_tag }} {%- assign pick_up_availabilities = product.selected_or_first_available_variant.store_availabilities | where: 'pick_up_enabled', true -%} <pickup-availability class="product__pickup-availabilities no-js-hidden" {% if product.selected_or_first_available_variant.available and pick_up_availabilities.size > 0 %} available{% endif %} data-base-url="{{ shop.url }}{{ routes.root_url }}" data-variant-id="{{ product.selected_or_first_available_variant.id }}" data-has-only-default-variant="{{ product.has_only_default_variant }}" > <template> <pickup-availability-preview class="pickup-availability-preview"> {% render 'icon-unavailable' %} <div class="pickup-availability-info"> <p class="caption-large">{{ 'products.product.pickup_availability.unavailable' | t }}</p> <button class="pickup-availability-button link link--text underlined-link">{{ 'products.product.pickup_availability.refresh' | t }}</button> </div> </pickup-availability-preview> </template> </pickup-availability> </div> <script src="{{ 'pickup-availability.js' | asset_url }}" defer="defer"></script> {%- when 'rating' -%} {%- if product.metafields.reviews.rating.value != blank -%} {% liquid assign rating_decimal = 0 assign decimal = product.metafields.reviews.rating.value.rating | modulo: 1 if decimal >= 0.3 and decimal <= 0.7 assign rating_decimal = 0.5 elsif decimal > 0.7 assign rating_decimal = 1 endif %} <div class="rating" role="img" aria-label="{{ 'accessibility.star_reviews_info' | t: rating_value: product.metafields.reviews.rating.value, rating_max: product.metafields.reviews.rating.value.scale_max }}"> <span aria-hidden="true" class="rating-star color-icon-{{ settings.accent_icons }}" style="--rating: {{ product.metafields.reviews.rating.value.rating | floor }}; --rating-max: {{ product.metafields.reviews.rating.value.scale_max }}; --rating-decimal: {{ rating_decimal }};"></span> </div> <p class="rating-text caption"> <span aria-hidden="true">{{ product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max }}</span> </p> <p class="rating-count caption"> <span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span> <span class="visually-hidden">{{ product.metafields.reviews.rating_count }} {{ "accessibility.total_reviews" | t }}</span> </p> {%- endif -%} {%- endcase -%} {%- endfor -%} </div> </div> </div> <product-modal id="ProductModal-{{ section.id }}" class="product-media-modal media-modal"> <div class="product-media-modal__dialog" role="dialog" aria-label="{{ 'products.modal.label' | t }}" aria-modal="true" tabindex="-1"> <button id="ModalClose-{{ section.id }}" type="button" class="product-media-modal__toggle" aria-label="{{ 'accessibility.close' | t }}">{% render 'icon-close' %}</button> <div class="product-media-modal__content" role="document" aria-label="{{ 'products.modal.label' | t }}" tabindex="0"> {%- liquid if product.selected_or_first_available_variant.featured_media != null assign media = product.selected_or_first_available_variant.featured_media render 'product-media', media: media, loop: section.settings.enable_video_looping, variant_image: section.settings.hide_variants endif -%} {%- for media in product.media -%} {%- liquid if section.settings.hide_variants and variant_images contains media.src assign variant_image = true else assign variant_image = false endif unless media.id == product.selected_or_first_available_variant.featured_media.id render 'product-media', media: media, loop: section.settings.enable_video_looping, variant_image: variant_image endunless -%} {%- endfor -%} </div> </div> </product-modal> {% assign popups = section.blocks | where: "type", "popup" %} {%- for block in popups -%} <modal-dialog id="PopupModal-{{ block.id }}" class="product-popup-modal" {{ block.shopify_attributes }}> <div role="dialog" aria-label="{{ block.settings.text }}" aria-modal="true" class="product-popup-modal__content" tabindex="-1"> <button id="ModalClose-{{ block.id }}" type="button" class="product-popup-modal__toggle" aria-label="{{ 'accessibility.close' | t }}">{% render 'icon-close' %}</button> <div class="product-popup-modal__content-info"> <h1 class="h2">{{ block.settings.page.title }}</h1> {{ block.settings.page.content }} </div> </div> </modal-dialog> {%- endfor -%} </section> {% javascript %} class ProductModal extends ModalDialog { constructor() { super(); } hide() { super.hide(); } show(opener) { super.show(opener); this.showActiveMedia(); } showActiveMedia() { this.querySelectorAll(`[data-media-id]:not([data-media-id="${this.openedBy.getAttribute("data-media-id")}"])`).forEach((element) => { element.classList.remove('active'); } ) const activeMedia = this.querySelector(`[data-media-id="${this.openedBy.getAttribute("data-media-id")}"]`); const activeMediaTemplate = activeMedia.querySelector('template'); const activeMediaContent = activeMediaTemplate ? activeMediaTemplate.content : null; activeMedia.classList.add('active'); activeMedia.scrollIntoView(); const container = this.querySelector('[role="document"]'); container.scrollLeft = (activeMedia.width - container.clientWidth) / 2; if (activeMedia.nodeName == 'DEFERRED-MEDIA' && activeMediaContent && activeMediaContent.querySelector('.js-youtube')) activeMedia.loadContent(); } } customElements.define('product-modal', ProductModal); {% endjavascript %} <script> document.addEventListener('DOMContentLoaded', function() { function isIE() { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); const trident = ua.indexOf('Trident/'); return (msie > 0 || trident > 0); } if (!isIE()) return; const hiddenInput = document.querySelector('#{{ product_form_id }} input[name="id"]'); const noScriptInputWrapper = document.createElement('div'); const variantSwitcher = document.querySelector('variant-radios[data-section="{{ section.id }}"]') || document.querySelector('variant-selects[data-section="{{ section.id }}"]'); noScriptInputWrapper.innerHTML = document.querySelector('.product-form__noscript-wrapper-{{ section.id }}').textContent; variantSwitcher.outerHTML = noScriptInputWrapper.outerHTML; document.querySelector('#Variants-{{ section.id }}').addEventListener('change', function(event) { hiddenInput.value = event.currentTarget.value; }); }); </script> {%- if first_3d_model -%} <script type="application/json" id="ProductJSON-{{ product.id }}"> {{ product.media | where: 'media_type', 'model' | json }} </script> <script src="{{ 'product-model.js' | asset_url }}" defer></script> {%- endif -%} <script type="application/ld+json"> { "@context": "http://schema.org/", "@type": "Product", "name": {{ product.title | json }}, "url": {{ shop.url | append: product.url | json }}, {%- if product.selected_or_first_available_variant.featured_media -%} {%- assign media_size = product.selected_or_first_available_variant.featured_media.preview_image.width | append: 'x' -%} "image": [ {{ product.selected_or_first_available_variant.featured_media | img_url: media_size | prepend: "https:" | json }} ], {%- endif -%} "description": {{ product.description | strip_html | json }}, {%- if product.selected_or_first_available_variant.sku != blank -%} "sku": {{ product.selected_or_first_available_variant.sku | json }}, {%- endif -%} "brand": { "@type": "Thing", "name": {{ product.vendor | json }} }, "offers": [ {%- for variant in product.variants -%} { "@type" : "Offer", {%- if variant.sku != blank -%} "sku": {{ variant.sku | json }}, {%- endif -%} "availability" : "http://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}", "price" : {{ variant.price | divided_by: 100.00 | json }}, "priceCurrency" : {{ cart.currency.iso_code | json }}, "url" : {{ shop.url | append: variant.url | json }} }{% unless forloop.last %},{% endunless %} {%- endfor -%} ] } </script> {% schema %} { "name": "t:sections.main-product.name", "tag": "section", "class": "product-section spaced-section", "blocks": [ { "type": "@app" }, { "type": "text", "name": "t:sections.main-product.blocks.text.name", "settings": [ { "type": "text", "id": "text", "default": "Text block", "label": "t:sections.main-product.blocks.text.settings.text.label" }, { "type": "select", "id": "text_style", "options": [ { "value": "body", "label": "t:sections.main-product.blocks.text.settings.text_style.options__1.label" }, { "value": "subtitle", "label": "t:sections.main-product.blocks.text.settings.text_style.options__2.label" }, { "value": "uppercase", "label": "t:sections.main-product.blocks.text.settings.text_style.options__3.label" } ], "default": "body", "label": "t:sections.main-product.blocks.text.settings.text_style.label" } ] }, { "type": "title", "name": "t:sections.main-product.blocks.title.name", "limit": 1 }, { "type": "price", "name": "t:sections.main-product.blocks.price.name", "limit": 1 }, { "type": "quantity_selector", "name": "t:sections.main-product.blocks.quantity_selector.name", "limit": 1 }, { "type": "variant_picker", "name": "t:sections.main-product.blocks.variant_picker.name", "limit": 1, "settings": [ { "type": "select", "id": "picker_type", "options": [ { "value": "dropdown", "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__1.label" }, { "value": "button", "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__2.label" } ], "default": "button", "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.label" } ] }, { "type": "buy_buttons", "name": "t:sections.main-product.blocks.buy_buttons.name", "limit": 1, "settings": [ { "type": "checkbox", "id": "show_dynamic_checkout", "default": true, "label": "t:sections.main-product.blocks.buy_buttons.settings.show_dynamic_checkout.label", "info": "t:sections.main-product.blocks.buy_buttons.settings.show_dynamic_checkout.info" } ] }, { "type": "description", "name": "t:sections.main-product.blocks.description.name", "limit": 1 }, { "type": "share", "name": "t:sections.main-product.blocks.share.name", "limit": 1, "settings": [ { "type": "text", "id": "share_label", "label": "t:sections.main-product.blocks.share.settings.text.label", "default": "Share" }, { "type": "paragraph", "content": "t:sections.main-product.blocks.share.settings.featured_image_info.content" }, { "type": "paragraph", "content": "t:sections.main-product.blocks.share.settings.title_info.content" } ] }, { "type": "custom_liquid", "name": "t:sections.main-product.blocks.custom_liquid.name", "settings": [ { "type": "liquid", "id": "custom_liquid", "label": "t:sections.main-product.blocks.custom_liquid.settings.custom_liquid.label", "info": "t:sections.main-product.blocks.custom_liquid.settings.custom_liquid.info" } ] }, { "type": "collapsible_tab", "name": "t:sections.main-product.blocks.collapsible_tab.name", "settings": [ { "type": "text", "id": "heading", "default": "Collapsible tab", "info": "t:sections.main-product.blocks.collapsible_tab.settings.heading.info", "label": "t:sections.main-product.blocks.collapsible_tab.settings.heading.label" }, { "type": "richtext", "id": "content", "label": "t:sections.main-product.blocks.collapsible_tab.settings.content.label" }, { "type": "page", "id": "page", "label": "t:sections.main-product.blocks.collapsible_tab.settings.page.label" }, { "type": "select", "id": "icon", "options": [ { "value": "none", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__1.label" }, { "value": "box", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__2.label" }, { "value": "chat_bubble", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__3.label" }, { "value": "check_mark", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__4.label" }, { "value": "dryer", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__5.label" }, { "value": "eye", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__6.label" }, { "value": "heart", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__7.label" }, { "value": "iron", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__8.label" }, { "value": "leaf", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__9.label" }, { "value": "leather", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__10.label" }, { "value": "lock", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__11.label" }, { "value": "map_pin", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__12.label" }, { "value": "pants", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__13.label" }, { "value": "plane", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__14.label" }, { "value": "price_tag", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__15.label" }, { "value": "question_mark", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__16.label" }, { "value": "return", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__17.label" }, { "value": "ruler", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__18.label" }, { "value": "shirt", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__19.label" }, { "value": "shoe", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__20.label" }, { "value": "silhouette", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__21.label" }, { "value": "star", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__22.label" }, { "value": "truck", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__23.label" }, { "value": "washing", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__24.label" } ], "default": "check_mark", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.label" } ] }, { "type": "popup", "name": "t:sections.main-product.blocks.popup.name", "settings": [ { "type": "text", "id": "text", "default": "Pop-up link text", "label": "t:sections.main-product.blocks.popup.settings.link_label.label" }, { "id": "page", "type": "page", "label": "t:sections.main-product.blocks.popup.settings.page.label" } ] }, { "type": "rating", "name": "t:sections.main-product.blocks.rating.name", "limit": 1, "settings": [ { "type": "paragraph", "content": "t:sections.main-product.blocks.rating.settings.paragraph.content" } ] } ], "settings": [ { "type": "select", "id": "slider_placement", "label": "slider placement", "default": "row-reverse", "options": [ { "value": "row-reverse", "label": "slider to left" }, { "value": "row", "label": "slider to right" } ] }, { "type": "header", "content": "t:sections.main-product.settings.header.content", "info": "t:sections.main-product.settings.header.info" }, { "type": "checkbox", "id": "hide_variants", "default": false, "label": "t:sections.main-product.settings.hide_variants.label" }, { "type": "checkbox", "id": "enable_video_looping", "default": false, "label": "t:sections.main-product.settings.enable_video_looping.label" } ] } {% endschema %} |
4. Next, we need to have the CSS. Open the Asset folder, then "create a new asset." Name it "product-slider."
5. Open the file, then place the code below.
.slider-container {
|
6. Still in the Asset folder, open the global.js. Then place the code below at the very bottom.
class VerticalSliderComponent extends HTMLElement { constructor() { super(); this.mainContainer = this.querySelector('.mainContainer'); this.mainSlider = this.querySelector('.mainBox '); this.sliderContainer = this.querySelector('.thumbContainer'); this.slider = this.querySelector('.thumbBox'); this.sliderItems = this.querySelectorAll('.thumbBoxItem'); this.pageCount = this.mainContainer.querySelector('.slider-counter--current'); this.pageTotal = this.mainContainer.querySelector('.slider-counter--total'); this.prevButtonMain = this.mainContainer.querySelector('button[name="previous"]'); this.nextButtonMain = this.mainContainer.querySelector('button[name="next"]'); this.prevButton = this.sliderContainer.querySelector('button[name="previous"]'); this.nextButton = this.sliderContainer.querySelector('button[name="next"]'); if (!this.slider || !this.nextButton) return; const resizeObserver = new ResizeObserver(entries => this.initPages()); resizeObserver.observe(this.slider); this.slider.addEventListener("wheel", e => e.preventDefault(), { passive:false }); this.slider.addEventListener('scroll', this.update.bind(this)); this.prevButtonMain.addEventListener('click', this.onMainClick.bind(this)); this.nextButtonMain.addEventListener('click', this.onMainClick.bind(this)); this.prevButton.addEventListener('click', this.onButtonClick.bind(this)); this.nextButton.addEventListener('click', this.onButtonClick.bind(this)); this.sliderItems.forEach(thumb => thumb.addEventListener('click', this.updateImage.bind(this))); this.index = 1; } initPages() { const sliderItemsToShow = Array.from(this.sliderItems).filter(element => element.clientHeight > 0); this.sliderLastItem = sliderItemsToShow[sliderItemsToShow.length - 1]; if (sliderItemsToShow.length === 0) return; this.slidesPerPage = Math.floor(this.slider.clientHeight / sliderItemsToShow[0].clientHeight); this.totalPages = sliderItemsToShow.length - this.slidesPerPage + 1; this.update(); this.zoom(); } update() { if (!this.pageCount || !this.pageTotal) return; this.currentPage = Math.round(this.slider.scrollTop / this.sliderLastItem.clientHeight) + 1; if (this.currentPage === 1) { this.prevButton.setAttribute('disabled', true); } else { this.prevButton.removeAttribute('disabled'); } if (this.currentPage === this.totalPages) { this.nextButton.setAttribute('disabled', true); } else { this.nextButton.removeAttribute('disabled'); } this.pageCount.textContent = this.currentPage; this.pageTotal.textContent = this.pageTotal.dataset.total; } onButtonClick(event) { event.preventDefault(); this.slideScrollPosition = event.currentTarget.name === 'next' ? this.slider.scrollTop + (4 * this.sliderLastItem.clientHeight) : this.slider.scrollTop - (4 * this.sliderLastItem.clientHeight); this.slider.scrollTo({ top: Math.floor(this.slideScrollPosition) }); this.currSlide = Math.round(this.slideScrollPosition / this.sliderLastItem.clientHeight); if (this.currSlide <= 0 ) { this.prevButton.setAttribute('disabled', true); } else { this.prevButton.removeAttribute('disabled'); } if (this.currSlide >= this.totalPages - 1 ) { this.nextButton.setAttribute('disabled', true); } else { this.nextButton.removeAttribute('disabled'); } } onMainClick(event) { event.preventDefault(); this.totalImage = this.pageTotal.dataset.total; if(event.currentTarget.name == 'next') { this.index++; if(this.index > this.totalImage) { this.index = 1; } } else { this.index--; if(this.index <= 0) { this.index = this.totalImage; } } this.updatePageCount(this.index); this.newImage = this.querySelector(`[data-media-index="${this.index}"]`); if(!this.newImage) return; this.parentData = this.newImage.parentElement; this.parentData.prepend(this.newImage); this.newImage.style.display = "block"; } updatePageCount(currentIndex) { this.pageCount.textContent = currentIndex; this.pageTotal.textContent = this.pageTotal.dataset.total; } updateImage(event) { this.clickedImage = event.currentTarget.dataset.thumbId; const newImage = document.querySelector(`[data-media-id="${this.clickedImage}"]`); const parentData = newImage.parentElement; parentData.prepend(newImage); newImage.style.display = "block"; this.sliderItems.forEach(thumb => thumb.querySelector('img').classList.remove('active-thumb')); event.currentTarget.querySelector('img').classList.add('active-thumb'); this.zoom(); } updateThumb(newMedia) { const newId = newMedia.dataset.mediaId; this.newThumb = this.querySelector(`[data-thumb-id="${newId}"]`); this.thumbLocate = this.newThumb["offsetTop"]; this.querySelectorAll('.slide-image').forEach(thumb => thumb. classList.remove('active-thumb')); this.querySelector(`[data-thumb-id="${newId}"] > img`).classList.add('active-thumb'); this.slider.scrollTo({top: this.thumbLocate}); this.zoom(); } zoom() { const mediaSmall = window.matchMedia('( min-width: 400px )'); const zoomItem = this.querySelector(".image-zoom:nth-child(1)") this.mainSlider.addEventListener("mouseout", function() { zoomItem.style.transform = 'scale(1)'; }); if (mediaSmall.matches) { this.mainSlider.addEventListener("mouseover", function() { zoomItem.style.transform = 'scale(1.8)'; }); this.mainSlider.addEventListener("mousemove", function(e) { const itemWidth = zoomItem.getBoundingClientRect().width; const itemLeft = zoomItem.getBoundingClientRect().left; const itemHt = zoomItem.getBoundingClientRect().height; const itemTop = zoomItem.getBoundingClientRect().top + window.scrollY; const translateX = Math.round((e.pageX - itemLeft ) / itemWidth * 100); const translateY = Math.round((e.pageY - itemTop) / itemHt * 100); zoomItem.style.transformOrigin = `${translateX}% ${translateY}%`; }); } } } customElements.define('vertical-slider-component', VerticalSliderComponent); |
7. Lastly, we need to find the "VariantSelect", under it find the updateMedia function. Before the window.setTimeout, insert the code below. Please refer to the image for placement.
this.verticalSlider = document.querySelector('vertical-slider-component'); this.verticalSlider.updateThumb(newMedia); |
That's it (",). If you have questions, feel free to contact me by using the "Chat with us."
1 comment
Hi, I was able to successfully create a vertical product slider thanks to your help! However, I was wondering if there’s a way to make the height of all of the product images the same, so that when I scroll through them on my phone, they don’t vary so much in height?