Compatability: Dawn 2.0 to Shopify 3.0
Add a quick view button with no app or external library. We will utilize Shopify's section rending API in this code.
Check DEMO store here 💻. Password: made4uo
To start:
1. Go to Admin Shopify store > Themes > Actions > Edit Code
2. In Section folder, create a new section, name it "quick-view", then replace the default code with the code below.
{{ 'quick-view.css' | asset_url | stylesheet_tag }} <product-viewer> <div class="hidden"> <div id="buttonViewer"> <div class="buttonViewer" style="background: {{section.settings.button_background}}; color: {{section.settings.button_font_color}}; font-size: clamp(12px, 1vw, 20px);"> <span class="desktopbutton"> <span>{{section.settings.button_label}}</span> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"> <style type="text/css"> .st--0{fill:none;} .st--1{fill: {{section.settings.button_font_color}};} </style> <g id="icon-eye"> <path class="st--0" d="M19.86,8.36c-5.96,0.26-11.2,3.82-15.25,8.02c-1.03,1.11-2,2.31-2.98,3.51c-0.33,0.41-0.31,0.96-0.01,1.42 c0.21,0.31,0.41,0.63,0.66,0.9c10.1,13.8,25.85,13.6,35.76-0.3C40.34,16.95,24.74,7.44,19.86,8.36z"/> <path class="st--1" d="M19.86,8.36c8.91,0.49,17.14,7.75,18.5,12.53c0.05,0.19-0.01,0.47-0.13,0.63 C28.31,35.31,12.34,35.96,2.27,22.2c-0.24-0.26-0.45-0.58-0.66-0.9c-0.3-0.45-0.32-1.01,0.01-1.42c0.98-1.19,1.95-2.4,2.98-3.51 C8.65,12.18,13.9,8.62,19.86,8.36z M20.06,11.35c-8.99-0.25-8.86,18.67-0.1,18.52C28.97,30.08,28.9,11.12,20.06,11.35z M26.06,29.26c4.06-1.67,7.57-4.94,10.49-8.36c-1.8-3.77-6.61-7.15-10.51-8.97C29.7,17.74,29.68,23.5,26.06,29.26z M13.99,11.95 c-0.03-0.04-0.03-0.03-0.06-0.07c-3.85,2.02-8.6,5.13-10.43,9.01c2.96,3.35,6.39,6.72,10.52,8.4 C10.36,23.49,10.37,17.72,13.99,11.95z"/> <path class="st--0" d="M20.06,11.35c8.84-0.23,8.91,18.73-0.1,18.52C11.2,30.02,11.07,11.1,20.06,11.35z M21.23,15.55 c-4.52-2.07-6.82,6.52-3.77,9.19c3.95,4.36,8.16-3.43,6.07-6.51C21.77,20.95,19.51,17.29,21.23,15.55z"/> <path class="st--0" d="M26.06,29.26c3.62-5.76,3.64-11.51-0.02-17.34c3.89,1.82,8.7,5.21,10.51,8.97 C33.64,24.32,30.12,27.58,26.06,29.26z"/> <path class="st--0" d="M13.98,11.96c-3.61,5.77-3.62,11.53,0.04,17.33c-4.13-1.68-7.56-5.05-10.52-8.4 c1.83-3.88,6.57-6.98,10.43-9.01C13.95,11.91,13.96,11.93,13.98,11.96z"/> <path class="st--1" d="M21.23,15.55c-1.72,1.75,0.54,5.39,2.3,2.68c2.1,3.09-2.11,10.87-6.07,6.51 C14.41,22.07,16.71,13.48,21.23,15.55z"/> </g> </svg> </span><span class="mobilebutton"> <svg viewBox="0 0 40 40" x="121" y="112"> <use xlink:href="#icon-eye" /> </svg> </span> </div> </div> </div> <div class="viewerBackground {% if template == 'page' %} view {% endif %}"> <div class="productViewContainer color-{{ section.settings.color_scheme }}"> <div class="quickViewButtonContainer"> <div class="navigatorWrapper"> <button type="button" class="prevNavigator navigatorBtn" name="prev" style="background: {{section.settings.button_background}}; color: {{section.settings.button_font_color}}">PREV</button> <button type="button" class="nextNavigator navigatorBtn" name="next" style="background: {{section.settings.button_background}}; color: {{section.settings.button_font_color}}">NEXT</button> <span class="errorMessage"></span> </div> <span class="productViewerClose" style=" stroke: {{section.settings.button_font_color}}; background: {{section.settings.button_background}}">{% render 'icon-close' %}</span> </div> <div class="productViewer"></div> </div> </div> </product-viewer> <script src="{{ 'product-form.js' | asset_url }}" defer="defer"></script> <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="{{ product.id }}-{{forloop.index0}}"]') || document.querySelector('variant-selects[data-section="{{ product.id }}-{{forloop.index0}}"]'); noScriptInputWrapper.innerHTML = document.querySelector('.product-form__noscript-wrapper-{{ product.id }}-{{forloop.index0}}').textContent; variantSwitcher.outerHTML = noScriptInputWrapper.outerHTML; document.querySelector('#Variants-{{ product.id }}').addEventListener('change', function(event) { hiddenInput.value = event.currentTarget.value; }); }); </script> <script type="application/ld+json"> { "@context": "http://schema.org/", "@type": "Product", "name": {{ product.title | json }}, "url": {{ shop.url | append: product.url | json }}, {% if seo_media -%} {%- assign media_size = seo_media.preview_image.width | append: 'x' -%} "image": [ {{ seo_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": "Quickview", "settings": [ { "type": "header", "content": "Quickview button style" }, { "type": "color", "id": "button_background", "label": "Button background", "default": "#000" }, { "type": "color", "id": "button_font_color", "label": "Button font color", "default": "#fff" }, { "type": "text", "id": "button_label", "label": "Button text", "default": "QUICKVIEW" }, { "type": "header", "content": "Pop-up modal style" }, { "type": "select", "id": "color_scheme", "options": [ { "value": "accent-1", "label": "Accent 1" }, { "value": "accent-2", "label": "Accent 2" }, { "value": "background-1", "label": "Background 1" }, { "value": "background-2", "label": "Background 2" }, { "value": "inverse", "label": "Inverse" } ], "default": "background-1", "label": "t:sections.all.colors.label" } ], "presets": [ { "name": "Quickview" } ] } {% endschema %} |
3. We need to create another section for our template. We are using Section Rendering API from Shopify. Name this newly created section "quick-view-template." Then paste the code below.
{{ 'section-main-product.css' | asset_url | stylesheet_tag }} {{ 'section-featured-product.css' | asset_url | stylesheet_tag }} {{ 'component-price.css' | asset_url | stylesheet_tag }} {{ 'component-rte.css' | asset_url | stylesheet_tag }} {{ 'component-loading-overlay.css' | asset_url | stylesheet_tag }} {{ 'quick-view.css' | asset_url | stylesheet_tag }} <link rel="stylesheet" href="{{ 'component-deferred-media.css' | asset_url }}" media="print" onload="this.media='all'"> {%- 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> <div class="section-{{ section.id }}-padding"> <div class="featured-product product grid grid--1-col gradient color-{{ section.settings.color_scheme }}{% if section.settings.secondary_background == false %} isolate{% endif %} {% if product.media.size > 0 %}grid--2-col-tablet{% else %}product--no-media{% endif %}"> <div class="grid__item product__media-wrapper"> {% if product %} <slider-quick-view> <ul class="largeImage"> {%- for media in product.media -%} <li class="large-image-item product__media-item grid__item slider__slide {% if forloop.first %} is-active {% endif %} {% if media.media_type != 'image' %} hide-modal {% endif %}" data-media-id="{{ media.id }}"> {% render 'product-thumbnail', media: media, position: forloop.index, loop: true, modal_id: product.id, xr_button: true %} </li> {%- endfor -%} </ul> <div class="slider-wrapper"> <button type="button" class="slider-button slider-button--prev bigger-slider {% if product.media.size <= 4 %}hidden{% endif %}" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}">{% render 'icon-caret' %}</button> <ul class="product-slider-box slider" role="list"> {%- assign variant_images = product.images| where: 'attached_to_variant?', true | map: 'src' -%} {%- for media in product.media -%} <li class="product-slider slider__slide" data-thumb-id="{{ media.id }}"> {% 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="{{ media.alt}}" style="aspect-ratio: 1;"> </div> {% else %} <img class="slide-image {% if forloop.first %}active-thumb {% endif %}" src="{{ media.preview_image | img_url: 'large' }}" alt="{{ media.alt }}" style="aspect-ratio: 1;"> {% endif %} </li> {% endfor %} </ul> <button type="button" class="slider-button slider-button--next bigger-slider {% if product.media.size <= 4 %}hidden{% endif %}" name="next" aria-label="{{ 'accessibility.next_slide' | t }}">{% render 'icon-caret' %}</button> </div> </slider-quick-view> {% endif %} </div> <div class="product__info-wrapper grid__item"> <div id="ProductInfo-{{ section.id }}" class="product__info-container"> {%- assign product_form_id = 'product-form-' | append: section.id -%} <h2 class="h2 product__title" {{ section.shopify_attributes }}> {%- if product.title != blank -%} {{ product.title | escape }} {%- else -%} {{ 'onboarding.product_title' | t }} {%- endif -%} </h2> <div class="no-js-hidden" id="price-{{ section.id }}" role="status" {{ 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 -%} {%- if product != blank -%} <div {{ block.shopify_attributes }}> {%- form 'product', product -%} <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}"> {{ form | payment_terms }} {%- endform -%} </div> {%- endif -%} <div class="product-form__input product-form__quantity{% if settings.inputs_shadow_vertical_offset != 0 and settings.inputs_shadow_vertical_offset < 0 %} product-form__quantity-top{% endif %}" {{ 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_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> <variant-viewer class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" data-update-url="false" {{ 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_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-viewer> <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> <div {{ block.shopify_attributes }}> {%- if product != blank -%} <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', novalidate: 'novalidate', 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 section.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 section.settings.show_dynamic_checkout -%} {{ form | payment_button }} {%- endif -%} </div> {%- endform -%} </product-form> {%- else -%} <div class="product-form"> <div class="product-form__buttons form"> <button type="submit" name="add" class="product-form__submit button button--full-width button--primary" disabled > {{ 'products.product.sold_out' | t }} </button> </div> </div> {%- endif -%} </div> {%- 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 -%} <a href="{{ product.url }}" class="link product__view-details animate-arrow"> {{ 'products.product.view_full_details' | t }} {% render 'icon-arrow' %} </a> </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 media_to_render contains media.id 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> </div> </section> {% javascript %} if (!customElements.get('product-modal')) { customElements.define('product-modal', 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(); } }); } {% endjavascript %} <script src="{{ 'product-form.js' | asset_url }}" defer="defer"></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 -%} {%- liquid if product.selected_or_first_available_variant.featured_media assign seo_media = product.selected_or_first_available_variant.featured_media else assign seo_media = product.featured_media endif -%} <script type="application/ld+json"> { "@context": "http://schema.org/", "@type": "Product", "name": {{ product.title | json }}, "url": {{ shop.url | append: product.url | json }}, {% if seo_media -%} {%- assign media_size = seo_media.preview_image.width | append: 'x' -%} "image": [ {{ seo_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> <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 product.media.size > 0 %} <script src="{{ 'media-gallery.js' | asset_url }}" defer="defer"></script> {% endif %} {% schema %} { "name": "Quick view template", "tag": "section", "class": "section section-featured-product", "settings": [ { "type": "header", "content": "t:sections.featured-product.settings.header.content", "info": "t:sections.featured-product.settings.header.info" }, { "type": "checkbox", "id": "hide_variants", "default": false, "label": "t:sections.main-product.settings.hide_variants.label" }, { "type": "checkbox", "id": "show_dynamic_checkout", "default": true, "label": "t:sections.featured-product.blocks.buy_buttons.settings.show_dynamic_checkout.label", "info": "t:sections.featured-product.blocks.buy_buttons.settings.show_dynamic_checkout.info" }, { "type": "text", "id": "share_label", "label": "t:sections.featured-product.blocks.share.settings.text.label", "default": "Share" } ], "presets": [ { "name": "Quick view template" } ] } {% endschema %} |
4. Next, find the Asset folder, and create a new CSS file. Name it "quick-view." Then open the newly created file and paste the code below.
.card-wrapper { position: relative; } .card-wrapper:hover .buttonViewer { display: flex; } .buttonViewer { width: 80%; max-height: 4rem; height: max-content; top: 33%; margin: auto; left: 0; right: 0; position: absolute; z-index: 4; display: none; align-items: center; justify-content: center; transition: all .3s ease; border-radius: 5px; padding: 3px; } .buttonViewer:hover { transform: scale(1.1); } .desktopbutton { display: flex; align-items: center; } .buttonViewer svg { max-width: 30px; width: 90%; margin: auto; aspect-ratio: 1; } .mobilebutton { display: none; } .productViewContainer { position: absolute; height: 65%; width: 60%; top: 0; left: 0; right: 0; bottom: 0; margin: auto; display: flex; flex-direction: column; overflow-y: auto; border-radius: 5px; } .productViewerClose { width: 30px; padding: 5px; border-radius: 3px; height: max-content; top: 15px; position: sticky; z-index: 1; display: flex; align-self: flex-end; transition: transform .2s ease; } .productViewerClose:hover { transform: scale(1.1); } .productViewerClose > svg { stroke-width: 1px; } .viewerBackground { position: fixed; width: 100vw; height: 100vh; top: 0; left: 0; bottom: 0; right: 0; background: rgba(0,0,0,0.7); z-index: 6; display: none; } .productViewer { display: flex; width: 100%; padding: 0% 5%; } .productViewer > section { width: 100%; } .productViewer .product__media-wrapper { width: 35%; height: max-content; } .productViewer .product__info-wrapper { padding: 0 !important; max-width: 65% !important; } .productViewer .featured-product { gap: 5% !important; } .largeImage { width: 70%; margin: auto; } .slider-wrapper img { width: 100%; } .productViewer:not(.product--no-media)>.product__info-wrapper { padding: 0 0 0 5% !important; } .cart__warnings a:after { z-index: 0; } .productViewContainer .is-active { padding: 0 !important; display: block !important; } .productViewContainer .product__media-item:not(.is-active) { display: none; } .productViewContainer .thumbnail-slider { display: flex !important; } .productViewContainer .thumbnail-list { margin: auto !important; } .productViewContainer .thumbnail-list.slider--tablet-up .thumbnail-list__item.slider__slide { width: calc(35% - 0.8rem) !important; aspect-ratio: 1; } .productViewContainer .slider-button { align-self: center; width: 25px; } .slider-button > .iconArrow { fill: black; } .slider-button[disabled] > .iconArrow { fill: rgba(var(--color-foreground),.3); cursor: not-allowed; } .quick-view-modal { display: flex !important; } .fixed { overflow: hidden !important; } @media only screen and (max-width: 750px) { .productViewContainer { width: 100%; } } @media only screen and (max-width: 481px) { .productViewer .featured-product { display: flex; flex-direction: column; flex-wrap: nowrap; } .productViewer .product__media-wrapper { width: 100% !important; margin: auto; max-width: 25rem; } .productViewer .product__info-wrapper { width: 100% !important; max-width: 100% !important; } .productViewer:not(.product--no-media)>.product__info-wrapper { padding: 0 !important; } .mobilebutton { display: flex; } .desktopbutton { display: none; } .desktopbutton > span { width: 100%; font-size: clamp(12px, 2vw, 20px); } .buttonViewer { width: 20%; height: 3rem; top: 5%; left: 75%; display: flex; } } .slider-container { margin: 0 auto; width: 90%; display: flex; flex-flow: column wrap; align-items: center; position: relative; } .large-image { width: 100%; height: 100%; position: relative; overflow:hidden; } .large-image-item { max-width: 100%; width: 100%; max-height: 100%; padding: 0; position: relative; margin: 0 auto; } .xoomImage { position: absolute; top: 0; left: 0; } .large-image-item:not(:first-child){ display: none !important; } ul { padding-inline-start: 0px !important; } slider-component { position: relative; display: block; width:100%; } .slider-wrapper { display: flex; justify-content: center; width: 100%; margin-top: 1rem; } .slider__slide { scroll-snap-align: start; flex-shrink: 0; } .product-slider-box { flex-wrap: inherit; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; -webkit-overflow-scrolling: touch; width: 100%; display: flex; z-index: 1; margin: 0 !important; } .product-slider { height: 100%; width: 25%; padding: 5px; display: inline-block; } .slide-image { width: 100%; object-fit: cover; } .slider { scrollbar-color: rgb(var(--color-foreground)) rgba(var(--color-foreground), 0.04); -ms-overflow-style: none; scrollbar-width: none; } .slider::-webkit-scrollbar { height: 0.4rem; width: 0.4rem; display: none; } .no-js .slider { -ms-overflow-style: auto; scrollbar-width: auto; } .no-js .slider::-webkit-scrollbar { display: initial; } .slider::-webkit-scrollbar-thumb { background-color: rgb(var(--color-foreground)); border-radius: 0.4rem; border: 0; } .slider::-webkit-scrollbar-track { background: rgba(var(--color-foreground), 0.04); border-radius: 0.4rem; } /*=== buttons === */ .button-wrapper { height: 100%; width: 105%; position: absolute; display: flex; justify-content: space-between; align-items: center; } .slider-container::-webkit-scrollbar { display: none; } .slider-buttons { display: flex; align-items: center; justify-content: center; } .slider-button { color: rgba(var(--color-foreground), 0.75); background: transparent; border: none; cursor: pointer; width: 44px; height: 44px; } .slider-button:not([disabled]):hover { color: rgb(var(--color-foreground)); } .slider-button .icon { height: 1.2rem; } .slider-button[disabled] .icon { color: rgba(var(--color-foreground), 0.3); } .slider-button--next .icon { margin-right: -0.2rem; transform: rotate(-90deg) translateX(0.15rem); } .slider-button--prev .icon { margin-left: -0.2rem; transform: rotate(90deg) translateX(-0.15rem); } .slider-button--next:not([disabled]):hover .icon { transform: rotate(-90deg) translateX(0.15rem) scale(1.07); } .slider-button--prev:not([disabled]):hover .icon { transform: rotate(90deg) translateX(-0.15rem) scale(1.07); } /* video button when image is a video type */ .video-btn { width: 100%; height: 100%; position: relative; } .v-btn { top: 2% !important; left: 2% !important; } .active-thumb { border: 3px solid white; outline: 2px solid black; } /* ==== custom codes in editing the product page ==== */ @media screen and (min-width: 750px) { .custom-width { padding: 0 10% !important; } } @media screen and (min-width: 990px) { .product__media-wrapper { max-width: 50% !important; width: calc(50% - 1rem / 2); } .product__info-wrapper { padding-left: 5rem !important; max-width: 50% !important; width: calc(50% - 1rem / 2); } } .quickViewButtonContainer { top: 5px; margin: 1rem; display: flex; justify-content: space-between; position: sticky; z-index: 2; } .errorMessage { margin-left: 5px; font-weight: 600; } .navigatorBtn { border-radius: 3px; height: 100%; padding: 1px 3px; margin-left: 5px; } .navigatorBtn:hover { background: red; } .navigatorBtn:disabled { cursor: not-allowed; opacity: 0.5; } /* hide modal */ .productViewer .product__media-icon { display: none !important; } .video-btn .product__media-icon { display: flex !important; } .productViewer .product__media-toggle:hover { cursor: pointer !important } .productViewer .slider-button > svg { stroke-width: 1px; height: 1.2rem !important; } |
5. Next, open the global.js file under the same folder, Asset and add the code below.
class SliderQuickView extends HTMLElement { constructor() { super(); this.slider = this.querySelector('.slider'); this.sliderItems = this.querySelectorAll('.product-slider'); this.pageCount = this.querySelector('.slider-counter--current'); this.pageTotal = this.querySelector('.slider-counter--total'); this.prevButton = this.querySelector('button[name="previous"]'); this.nextButton = this.querySelector('button[name="next"]'); if (!this.slider || !this.nextButton) return; const resizeObserver = new ResizeObserver(entries => this.initPages()); resizeObserver.observe(this.slider); this.slider.addEventListener('scroll', this.update.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.imageId.bind(this))); } initPages() { const sliderItemsToShow = Array.from(this.sliderItems).filter(element => element.clientWidth > 0); this.sliderLastItem = sliderItemsToShow[sliderItemsToShow.length - 1]; if (sliderItemsToShow.length === 0) return; this.slidesPerPage = Math.floor(this.slider.clientWidth / sliderItemsToShow[0].clientWidth); this.totalPages = sliderItemsToShow.length - this.slidesPerPage + 1; this.update(); } update() { if (!this.pageCount || !this.pageTotal) return; this.currentPage = Math.round(this.slider.scrollLeft / this.sliderLastItem.clientWidth) + 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.totalPages; } onButtonClick(event) { event.preventDefault(); const slideScrollPosition = event.currentTarget.name === 'next' ? this.slider.scrollLeft + (this.slider.clientWidth): this.slider.scrollLeft - (this.slider.clientWidth); this.slider.scrollTo({ left: Math.floor(slideScrollPosition) }); this.currSlide = Math.round(slideScrollPosition / this.slider.clientWidth); if (this.currSlide === 0 ) { this.prevButton.setAttribute('disabled', true); } else { this.prevButton.removeAttribute('disabled'); } if (this.currSlide === this.totalPages / 3 ) { this.nextButton.setAttribute('disabled', true); } else { this.nextButton.removeAttribute('disabled'); } } imageId(e) { this.clickedImage = e.currentTarget.dataset.thumbId; this.imageUpdate(this.clickedImage ); this.thumbUpdate(this.clickedImage); } imageUpdate(image){ this.newImage = this.querySelector(`[data-media-id="${image}"]`); this.parentData = this.newImage.parentElement; this.parentData.prepend(this.newImage); this.newImage.style.display = "block"; } thumbUpdate(variantImage) { this.activeThumb = this.querySelector(`[data-thumb-id='${variantImage}']`); this.sliderItems.forEach(slider => slider.querySelector('img').classList.remove('active-thumb')); this.activeThumb.scrollIntoView({block: "nearest", inline: "center"}); this.activeThumb.querySelector('img').classList.add('active-thumb'); } } customElements.define('slider-quick-view', SliderQuickView); class ButtonViewer extends HTMLElement { constructor() { super(); this.productViewer = document.querySelector('product-viewer'); this.attachQuickView(); this.addEventListener('click', this.passId.bind(this)); this.prevProduct; this.nextProduct; this.buttons = document.querySelectorAll('button-viewer'); this.indexLength; } attachQuickView() { this.closest('ul').querySelectorAll('button-viewer').forEach((button, index) => { button.setAttribute('data-view-index', index); }); this.indexLength = this.closest('ul').querySelectorAll('button-viewer').length; this.innerHTML = this.productViewer.querySelector('#buttonViewer').innerHTML; this.insertAdjacentHTML('afterbegin', this.domParser(this.innerHTML)); } domParser(html) { return new DOMParser() .parseFromString(html, 'text/html') .querySelector('body').innerHTML; } passId(event) { this.productHandle = event.currentTarget.dataset.product; this.productViewer.openViewer( this.productHandle); this.assignIndex = event.currentTarget.dataset.viewIndex; this.productViewer.currentViewer(this.closest('ul'), this.assignIndex, this.indexLength); } } customElements.define('button-viewer', ButtonViewer); class ProductViewer extends HTMLElement { constructor() { super(); this.productViewerContainer= this.querySelector('.viewerBackground'); this.productViewer = this.querySelector('.productViewer'); this.buttonViewerClose = this.querySelector('.productViewerClose'); this.buttonViewerClose.addEventListener('click', this.closeViewer.bind(this)); this.prevButton = this.querySelector('.prevNavigator'); this.nextButton = this.querySelector('.nextNavigator'); this.errorContainer = this.querySelector('.errorMessage'); this.prevButton.addEventListener('click', this.onButtonClick.bind(this)); this.nextButton.addEventListener('click', this.onButtonClick.bind(this)); this.changeEvent = new Event('change', { bubbles: true }) this.current; this.parent; this.indexLength; } openViewer(productHandle) { this.fetchProduct(productHandle); document.body.classList.toggle('fixed'); this.productViewerContainer.classList.toggle('quick-view-modal '); } fetchProduct(productHandle) { fetch(`/products/${productHandle}?section_id=quick-view-template`) .then(response => response.text()) .then(data => { this.parsed = this.domParser(data); this.productViewer.innerHTML = this.parsed; }); } domParser(html) { return new DOMParser() .parseFromString(html, 'text/html') .querySelector('body').innerHTML; } closeViewer() { this.productViewerContainer.classList.toggle('quick-view-modal '); document.body.classList.toggle('fixed'); } onButtonClick(event) { event.preventDefault(); event.target.getAttribute('name') == 'next' ? this.current = this.current + 1 : this.current = this.current - 1; if(this.current == 0) { this.errorMessage = 'This is the first product.'; this.prevButton.disabled = true; } else if(this.current == this.indexLength) { this.errorMessage = 'This is the last product.'; this.nextButton.disabled = true; } else { this.errorMessage = ' '; this.prevButton.disabled = false; this.nextButton.disabled = false; } this.errorContainer.innerHTML = this.errorMessage; this.navigated = this.parent.querySelector(`[data-view-index='${this.current}']`); if(!this.navigated) return; this.fetchProduct(this.navigated.dataset.product); } currentViewer(currentParent, currentIndex, indexLength) { this.parent = currentParent; this.current = (currentIndex * 1); this.indexLength = indexLength - 1; } } customElements.define('product-viewer', ProductViewer); class variantViewer extends VariantSelects { constructor() { super(); this.productViewer = document.querySelector('slider-quick-view'); } updateMedia() { if (!this.currentVariant) return; if (!this.currentVariant.featured_media) return; this.variantMedia = this.currentVariant.featured_media.id; this.productViewer.imageUpdate(this.variantMedia); this.productViewer.thumbUpdate(this.variantMedia); } } customElements.define('variant-viewer', variantViewer); |
6. We need to call this quick-view in the product card. Go to Snippet folder, if you have Dawn Theme version 2.5 above, open the card-product.liquid. Find the class "card-wrapper", then paste the code, after the line.
<button-viewer class="quickViewBtn" data-product="{{card_product.handle}}" ></button-viewer> |
For Dawn Theme 2.5 and lower, follow same instruction but paste the code below instead.
<button-viewer class="quickViewBtn" data-product="{{product_card_product.handle}}" ></button-viewer> |
7. Last thing, go to the theme.liquid folder on top. Then find the {% section 'announcement-bar' %}, paste the code above it.
{% section 'quick-view' %} |
That's it. You can customize the the quick-view section if you go to Theme Editor. The Section should be at the very top. (",)