Add snippets/card-gallery.liquid
This commit is contained in:
1 parent
8364acc7e8
commit
9c74274506
1 file changed
+488
@@ -0,0 +1,488 @@
|
|||||||
|
{%- doc -%}
|
||||||
|
Displays product images in a carousel.
|
||||||
|
Settings allow for a full slideshow, showing only the first image, or showing the second image when hovering.
|
||||||
|
Note: When the product card itself is in a carousel layout, the card-gallery's carousel is disabled with JavaScript.
|
||||||
|
|
||||||
|
@param [children] - Additional content rendered below the card gallery
|
||||||
|
@param [section] - The section object the snippet is rendered in
|
||||||
|
@param [block] - The block object the snippet is rendered in
|
||||||
|
{%- enddoc -%}
|
||||||
|
|
||||||
|
{% liquid
|
||||||
|
assign product = closest.product
|
||||||
|
assign block_settings = block.settings
|
||||||
|
assign image_sizes = '(min-width: 750px) 50vw, 100vw'
|
||||||
|
|
||||||
|
# Check if product has badges and determine position
|
||||||
|
assign has_badges = false
|
||||||
|
assign badge_position = null
|
||||||
|
if product != blank
|
||||||
|
comment
|
||||||
|
start Yagi
|
||||||
|
endcomment
|
||||||
|
assign product_compare_at_price = product.compare_at_price
|
||||||
|
assign product_price = product.price
|
||||||
|
|
||||||
|
assign product_price = product.metafields.app--168074346497.min_auto_discounted_price.value | default: product.price
|
||||||
|
|
||||||
|
if product.metafields.app--168074346497.discount_percentage.value > 0.01
|
||||||
|
assign deducted_percentage = 1.0 | minus: product.metafields.app--168074346497.discount_percentage.value
|
||||||
|
|
||||||
|
assign product_price = product.price | divided_by: 100.0 | times: deducted_percentage | times: 100.0 | ceil
|
||||||
|
|
||||||
|
assign product_compare_at_price = product.price
|
||||||
|
|
||||||
|
if product.compare_at_price > product_compare_at_price
|
||||||
|
assign product_compare_at_price = product.compare_at_price
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
comment
|
||||||
|
end Yagi
|
||||||
|
endcomment
|
||||||
|
|
||||||
|
if product.available == false or product_compare_at_price > product_price
|
||||||
|
assign has_badges = true
|
||||||
|
assign badge_position = settings.badge_position
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
# if the card-gallery has a section.settings.product_card_size:
|
||||||
|
# assume grid-template autofill(card-size, 1fr) and calculate the sizes attribute based on the minimum card size
|
||||||
|
#
|
||||||
|
# if section has section.settings.columns:
|
||||||
|
# assume grid-template repeat(column-count, 1fr) and calculate the sizes attribute based on the number of columns
|
||||||
|
if section.settings.product_card_size
|
||||||
|
assign mobile_image_size = '100vw'
|
||||||
|
if section.settings.mobile_product_card_size == 'small'
|
||||||
|
assign mobile_image_size = '50vw'
|
||||||
|
endif
|
||||||
|
|
||||||
|
capture card_size
|
||||||
|
render 'util-product-grid-card-size', section: section
|
||||||
|
endcapture
|
||||||
|
assign card_size = card_size | strip | replace: 'px', '' | plus: 0
|
||||||
|
capture sizes_attribute
|
||||||
|
render 'util-autofill-img-size-attr', card_size: card_size, card_gap: section.settings.columns_gap_horizontal, mobile_size: mobile_image_size
|
||||||
|
endcapture
|
||||||
|
assign image_sizes = sizes_attribute | strip
|
||||||
|
elsif section.settings.columns and section.settings.layout_type != 'editorial'
|
||||||
|
assign viewport_width = 100.0 | divided_by: section.settings.columns
|
||||||
|
assign sizes_attribute = '(min-width: 750px) [viewport_width]vw, 100vw' | replace: '[viewport_width]', viewport_width
|
||||||
|
assign image_sizes = sizes_attribute | strip
|
||||||
|
endif
|
||||||
|
|
||||||
|
assign ratio = '1'
|
||||||
|
case block_settings.image_ratio
|
||||||
|
when 'landscape'
|
||||||
|
assign ratio = '16 / 9'
|
||||||
|
when 'portrait'
|
||||||
|
assign ratio = '4 / 5'
|
||||||
|
when 'square'
|
||||||
|
assign ratio = '1'
|
||||||
|
when 'adapt'
|
||||||
|
assign ratio = product.featured_media.preview_image.aspect_ratio | default: '1'
|
||||||
|
endcase
|
||||||
|
|
||||||
|
if block_settings.image_ratio == 'adapt' and block_settings.constrain_to_viewport
|
||||||
|
if block_settings.constrain_to_viewport != ''
|
||||||
|
assign constrain_to_viewport = true
|
||||||
|
assign media_fit = block_settings.constrain_to_viewport
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
%}
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref="cardGallery"
|
||||||
|
class="card-gallery card-gallery-{{ block.id }} spacing-style border-style{% if has_badges %} card-gallery--badge-{{ badge_position }}{% endif %}"
|
||||||
|
style="
|
||||||
|
{% render 'border-override', settings: block_settings %}
|
||||||
|
{% render 'spacing-style', settings: block_settings %}
|
||||||
|
--gallery-aspect-ratio: {{ ratio }};
|
||||||
|
"
|
||||||
|
data-product-id="{{ product.id }}"
|
||||||
|
{% if settings.show_second_image_on_hover and product.media.size > 1 %}
|
||||||
|
on:pointerenter="/previewImage"
|
||||||
|
on:pointerleave="/resetImage"
|
||||||
|
{% endif %}
|
||||||
|
{% if settings.transition_to_main_product %}
|
||||||
|
data-view-transition-to-main-product
|
||||||
|
{% endif %}
|
||||||
|
data-image-ratio="{{ block_settings.image_ratio }}"
|
||||||
|
{{ block.shopify_attributes }}
|
||||||
|
>
|
||||||
|
{%- if product.media.size > 0 -%}
|
||||||
|
<a
|
||||||
|
class="contents"
|
||||||
|
ref="cardGalleryLink"
|
||||||
|
href="{{ product.url }}"
|
||||||
|
aria-label="{{- product.title -}}"
|
||||||
|
>
|
||||||
|
{% liquid
|
||||||
|
assign featured_media = product.featured_media
|
||||||
|
|
||||||
|
assign featured_media_loading = 'lazy'
|
||||||
|
assign featured_media_sizes = 'auto, ' | append: image_sizes
|
||||||
|
if section.index == null or section.index < 5
|
||||||
|
assign featured_media_loading = 'eager'
|
||||||
|
assign featured_media_sizes = image_sizes
|
||||||
|
endif
|
||||||
|
|
||||||
|
# The class differs per media only by media_type; the base template and the
|
||||||
|
# constrain-height suffix are the same for every slide, so build them once.
|
||||||
|
assign media_class = 'product-media-container media-fit product-media-container--[media_type]'
|
||||||
|
assign media_class_suffix = ''
|
||||||
|
if constrain_to_viewport
|
||||||
|
assign media_class_suffix = ' constrain-height'
|
||||||
|
endif
|
||||||
|
|
||||||
|
assign featured_media_class = media_class | replace: '[media_type]', featured_media.media_type | append: media_class_suffix
|
||||||
|
%}
|
||||||
|
{%- if product.media.size == 1 -%}
|
||||||
|
<div
|
||||||
|
class="{{ featured_media_class }}"
|
||||||
|
style="--product-media-fit: {{ media_fit | default: 'cover' }};"
|
||||||
|
>
|
||||||
|
{% render 'product-media',
|
||||||
|
media: featured_media,
|
||||||
|
sizes: featured_media_sizes,
|
||||||
|
loading: featured_media_loading,
|
||||||
|
preview_image_only: true
|
||||||
|
%}
|
||||||
|
</div>
|
||||||
|
{%- else -%}
|
||||||
|
{% liquid
|
||||||
|
# Check if there are generic media that are not variant images
|
||||||
|
assign variant_images = product.images | where: 'attached_to_variant?', true | map: 'src'
|
||||||
|
assign generic_media_size = product.media.size | minus: variant_images.size
|
||||||
|
|
||||||
|
# Sort media so product.featured_media.preview_image comes first as that is the most relevant image to display
|
||||||
|
# product.featured_media updates based on search context and filters applied
|
||||||
|
|
||||||
|
assign other_media = product.media | reject: 'src', product.featured_media
|
||||||
|
assign media_list_to_show = product.featured_media | concat: other_media
|
||||||
|
|
||||||
|
# Don't overfetch media if we don't need to
|
||||||
|
assign max_visible_slide_count = 1
|
||||||
|
if settings.product_card_carousel
|
||||||
|
assign max_visible_slide_count = 5
|
||||||
|
elsif settings.show_second_image_on_hover
|
||||||
|
assign max_visible_slide_count = 2
|
||||||
|
endif
|
||||||
|
|
||||||
|
capture slides
|
||||||
|
for media in media_list_to_show
|
||||||
|
assign hidden = false
|
||||||
|
assign attributes = ''
|
||||||
|
if variant_images contains media.src
|
||||||
|
assign attributes = 'variant-image'
|
||||||
|
|
||||||
|
if forloop.first == false
|
||||||
|
# hide all variant images that aren't the first one
|
||||||
|
assign hidden = true
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Performance improvement: don't render more than 5 visible images into the slideshow
|
||||||
|
# We'll keep the images that we need for the variant images and hover next.
|
||||||
|
if forloop.first
|
||||||
|
assign visible_slide_count = 0
|
||||||
|
endif
|
||||||
|
unless hidden
|
||||||
|
assign visible_slide_count = visible_slide_count | plus: 1
|
||||||
|
endunless
|
||||||
|
if hidden == false and visible_slide_count > max_visible_slide_count
|
||||||
|
continue
|
||||||
|
endif
|
||||||
|
|
||||||
|
if forloop.first
|
||||||
|
assign loading = featured_media_loading
|
||||||
|
assign sizes = featured_media_sizes
|
||||||
|
else
|
||||||
|
assign loading = 'lazy'
|
||||||
|
assign sizes = 'auto, ' | append: image_sizes
|
||||||
|
endif
|
||||||
|
|
||||||
|
assign class = media_class | replace: '[media_type]', media.media_type | append: media_class_suffix
|
||||||
|
|
||||||
|
capture slideshow_children
|
||||||
|
render 'product-media', media: media, sizes: sizes, loading: loading, preview_image_only: true
|
||||||
|
endcapture
|
||||||
|
|
||||||
|
render 'slideshow-slide', slide_id: media.id, index: forloop.index0, hidden: hidden, class: class, attributes: attributes, children: slideshow_children, media_fit: media_fit
|
||||||
|
endfor
|
||||||
|
endcapture
|
||||||
|
|
||||||
|
# Arrows can only reach slides that are rendered visible. Variant images past
|
||||||
|
# the featured media are hidden, so a product whose only extra media are
|
||||||
|
# variant images has a single navigable slide and would get inert arrows.
|
||||||
|
if product.media.size > 2 and visible_slide_count > 1
|
||||||
|
assign show_arrows = true
|
||||||
|
endif
|
||||||
|
|
||||||
|
assign slideshow_attributes = 'data-generic-media-size="[generic_media_size]"' | replace: '[generic_media_size]', generic_media_size
|
||||||
|
if settings.product_card_carousel == false
|
||||||
|
assign slideshow_attributes = slideshow_attributes | append: ' disabled="true"'
|
||||||
|
endif
|
||||||
|
%}
|
||||||
|
{% render 'slideshow',
|
||||||
|
ref: 'slideshow',
|
||||||
|
initial_slide: 0,
|
||||||
|
slides: slides,
|
||||||
|
slide_count: media_list_to_show.size,
|
||||||
|
show_arrows: show_arrows,
|
||||||
|
attributes: slideshow_attributes
|
||||||
|
%}
|
||||||
|
{%- endif -%}
|
||||||
|
</a>
|
||||||
|
{% elsif product != blank and product.media.size == 0 %}
|
||||||
|
<div class="product-card-gallery__title-placeholder">
|
||||||
|
<a
|
||||||
|
class="contents"
|
||||||
|
ref="cardGalleryLink"
|
||||||
|
href="{{ product.url }}"
|
||||||
|
>
|
||||||
|
<span class="title-text">{{- product.title -}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% elsif product == blank %}
|
||||||
|
{% liquid
|
||||||
|
capture placeholder_image
|
||||||
|
# Extract index from block ID (e.g., "product-card-3" -> "3")
|
||||||
|
assign block_parts = block.id | split: '-'
|
||||||
|
assign placeholder_index = block_parts.last | default: 0
|
||||||
|
assign variant = placeholder_index | modulo: 3 | plus: 1
|
||||||
|
assign placeholder_name = 'product-apparel-' | append: variant
|
||||||
|
echo placeholder_name | placeholder_svg_tag
|
||||||
|
endcapture
|
||||||
|
capture placeholder_slide
|
||||||
|
render 'slideshow-slide', index: 1, children: placeholder_image, class: 'product-media-container card-gallery__placeholder'
|
||||||
|
endcapture
|
||||||
|
render 'slideshow', ref: 'slideshow', slides: placeholder_slide, slide_count: 1, attributes: 'disabled="true"'
|
||||||
|
%}
|
||||||
|
{%- endif -%}
|
||||||
|
{{ children }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% render 'slideshow-styles' %}
|
||||||
|
{% render 'product-media-container-styles' %}
|
||||||
|
|
||||||
|
{% stylesheet %}
|
||||||
|
.card-gallery {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
container-type: inline-size; /* Make card-gallery a container */
|
||||||
|
container-name: card-gallery-container; /* Optional: name the container */
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 70px) {
|
||||||
|
.card-gallery:hover .quick-add__button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-gallery__placeholder svg {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-gallery svg {
|
||||||
|
aspect-ratio: var(--gallery-aspect-ratio, var(--ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding: var(--padding-md);
|
||||||
|
font-size: var(--font-size--2xl);
|
||||||
|
line-height: var(--line-height--display-loose);
|
||||||
|
word-break: break-word;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
background-color: rgb(var(--color-foreground-rgb) / var(--opacity-5));
|
||||||
|
aspect-ratio: var(--gallery-aspect-ratio);
|
||||||
|
border-radius: var(--product-corner-radius);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-gallery__title-placeholder .title-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 5;
|
||||||
|
line-clamp: 5;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-gallery[data-image-ratio='landscape'] .product-card-gallery__title-placeholder .title-text {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
line-clamp: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 750px) {
|
||||||
|
.product-grid[data-product-card-size='extra-large'] .product-card-gallery__title-placeholder {
|
||||||
|
padding: var(--padding-3xl);
|
||||||
|
font-size: var(--font-size--3xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='large'] .product-card-gallery__title-placeholder {
|
||||||
|
padding: var(--padding-2xl);
|
||||||
|
font-size: var(--font-size--2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='medium'] .product-card-gallery__title-placeholder {
|
||||||
|
padding: var(--padding-xl);
|
||||||
|
font-size: var(--font-size--xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='small'] .product-card-gallery__title-placeholder {
|
||||||
|
padding: var(--padding-sm);
|
||||||
|
font-size: var(--font-size--lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='extra-large']
|
||||||
|
.card-gallery.card-gallery--badge-top-right
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-inline-end: calc(var(--padding-3xl) + 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='large']
|
||||||
|
.card-gallery.card-gallery--badge-top-right
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-inline-end: calc(var(--padding-2xl) + 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='medium']
|
||||||
|
.card-gallery.card-gallery--badge-top-right
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-inline-end: calc(var(--padding-xl) + 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='small']
|
||||||
|
.card-gallery.card-gallery--badge-top-right
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-inline-end: calc(var(--padding-sm) + 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='extra-large']
|
||||||
|
.card-gallery.card-gallery--badge-top-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-top: calc(var(--padding-3xl) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='large']
|
||||||
|
.card-gallery.card-gallery--badge-top-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-top: calc(var(--padding-2xl) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='medium']
|
||||||
|
.card-gallery.card-gallery--badge-top-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-top: calc(var(--padding-xl) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='small']
|
||||||
|
.card-gallery.card-gallery--badge-top-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-top: calc(var(--padding-sm) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='extra-large']
|
||||||
|
.card-gallery.card-gallery--badge-bottom-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-bottom: calc(var(--padding-3xl) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='large']
|
||||||
|
.card-gallery.card-gallery--badge-bottom-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-bottom: calc(var(--padding-2xl) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='medium']
|
||||||
|
.card-gallery.card-gallery--badge-bottom-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-bottom: calc(var(--padding-xl) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size='small']
|
||||||
|
.card-gallery.card-gallery--badge-bottom-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-bottom: calc(var(--padding-sm) + 40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 749px) {
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
font-size: var(--font-size--xl);
|
||||||
|
padding: var(--padding-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size]
|
||||||
|
.card-gallery.card-gallery--badge-top-right
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-inline-end: calc(var(--padding-sm) + 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size]
|
||||||
|
.card-gallery.card-gallery--badge-top-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-top: calc(var(--padding-sm) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-product-card-size]
|
||||||
|
.card-gallery.card-gallery--badge-bottom-left
|
||||||
|
.product-card-gallery__title-placeholder {
|
||||||
|
padding-bottom: calc(var(--padding-sm) + 40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[product-grid-view='zoom-out'] .card-gallery .product-card-gallery__title-placeholder {
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
padding: var(--padding-xs) !important;
|
||||||
|
font-size: var(--font-size--xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
[product-grid-view='zoom-out'] .card-gallery .slideshow-control {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul[product-grid-view='zoom-out'] .product-grid__card .card-gallery {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
[product-grid-view='zoom-out']
|
||||||
|
.card-gallery
|
||||||
|
> :is(quick-add-component, .product-badges, slideshow-component > slideshow-controls) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul[product-grid-view='zoom-out'] .card-gallery > img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-gallery slideshow-arrows .slideshow-control {
|
||||||
|
/* Align icons with quick-add button */
|
||||||
|
padding-inline: var(--padding-xl);
|
||||||
|
|
||||||
|
@container (max-width: 249px) {
|
||||||
|
padding-inline: 0 var(--padding-sm);
|
||||||
|
|
||||||
|
[dir='rtl'] & {
|
||||||
|
padding-inline: var(--padding-sm) 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Card galleries preview the next or previous images on 'pointerenter', so we
|
||||||
|
* try to kick load them beforehand (they are lazy loaded otherwise).
|
||||||
|
*/
|
||||||
|
.card-gallery
|
||||||
|
:is(
|
||||||
|
slideshow-slide:has(+ slideshow-slide[aria-hidden='false']),
|
||||||
|
slideshow-slide[aria-hidden='false'] + slideshow-slide
|
||||||
|
) {
|
||||||
|
content-visibility: auto;
|
||||||
|
|
||||||
|
slideshow-component[actioned] & {
|
||||||
|
content-visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endstylesheet %}
|
||||||
Reference in new issue
Block a user