I have a popover-size selection in my Shopify shop, but after opening the popover for the first time and selecting a size, I experience an unsightly rerender. This issue only occurs on Android when I first open the page, after that it works fine. When I close and reopen the tab, it happens again.
{%- assign color_label_list = 'general.label.color' | t | replace: ', ', ',' | downcase | split: ',' -%}
{%- assign size_label_list = 'general.label.size' | t | replace: ', ', ',' | downcase | split: ',' -%}
{%- assign variant_image_options = block.settings.variant_image_options | replace: ', ', ',' | downcase | split: ',' -%}
{{ 'variant-picker-custom.css' | asset_url | stylesheet_tag }}
<script>
function selectVariant(optionValueId, element, event) {
// Prevent default behavior and stop propagation
if (event) {
event.preventDefault();
event.stopPropagation();
}
// Find the radio input and check it
const radioInput = document.querySelector(`input[value="${optionValueId}"]`);
if (radioInput) {
radioInput.checked = true;
// Get the form
const form = document.getElementById('{{ form_id }}');
if (form) {
// Get all the checked option inputs
const checkedOptions = Array.from(form.elements)
.filter(item => item.matches('input[data-option-position]:checked'))
.sort((a, b) => parseInt(a.getAttribute('data-option-position')) - parseInt(b.getAttribute('data-option-position')));
// Get the variant picker component
const variantPicker = document.querySelector('variant-picker');
if (variantPicker) {
const optionValues = checkedOptions.map(input => input.value);
// Trigger the variant picker's selectCombination method which will handle everything
variantPicker.selectCombination({
optionValues,
productChange: false
});
}
// Update add to cart button state in the drawer
const drawer = element.closest('x-drawer');
if (drawer) {
const drawerSubmitButton = drawer.querySelector('[slot="footer"] button[type="submit"]');
if (drawerSubmitButton) {
const isAvailable = !element.classList.contains('is-disabled');
drawerSubmitButton.disabled = !isAvailable;
}
}
}
// Update selected state styling for options within the same drawer
const drawer = element.closest('x-drawer');
if (drawer) {
const allOptions = drawer.querySelectorAll('.variant-grid__option');
allOptions.forEach(opt => {
opt.classList.remove('is-selected');
});
element.classList.add('is-selected');
}
}
}
</script>
{%- unless product.has_only_default_variant -%}
<variant-picker class="variant-picker" section-id="{{ section.id }}" handle="{{ product.handle }}" form-id="{{ form_id }}" {% if update_url %}update-url{% endif %}>
{%- comment -%}
The variant data is outputted as a JSON, which allows the theme to emit an event with the data when the variant changes. This must not be removed.
{%- endcomment -%}
<script data-variant type="application/json">
{{- product.selected_or_first_available_variant | json -}}
</script>
{%- for option in product.options_with_values -%}
{% liquid
assign option_downcase = option.name | downcase
assign resolved_option_selector_style = block.settings.selector_style
# Create a map of option values to their fast shipping status
assign fast_shipping_values = ''
for variant in product.variants
if variant.inventory_management == 'shopify'
assign option_position = option.position | minus: 1
assign variant_option_value = variant.options[option_position]
assign fast_shipping_values = fast_shipping_values | append: '|' | append: variant_option_value
endif
endfor
assign fast_shipping_values = fast_shipping_values | remove_first: '|' | split: '|'
assign swatch_count = option.values | map: 'swatch' | compact | size
if swatch_count > 0 and block.settings.swatch_selector_style != 'none'
# Use the swatch selector type only if we have at least one swatch and a supported swatch selector type
assign resolved_option_selector_style = block.settings.swatch_selector_style
endif
# Implementation note: if the option value has no native swatch, BUT that the option name matches a hardcoded list of color names,
# we fallback to the legacy config-based system. This allows to keep compatibility with stores that were using the config-based, and
# allow those merchants to upgrade to the new system at their own pace.
if swatch_count == 0 and color_label_list contains option_downcase and block.settings.swatch_selector_style != 'none'
assign resolved_option_selector_style = block.settings.swatch_selector_style
endif
if resolved_option_selector_style == 'dropdown' and force_dropdown_as_block
assign resolved_option_selector_style = 'block'
endif
if variant_image_options contains option_downcase
assign resolved_option_selector_style = 'variant_image'
endif
%}
<fieldset class="variant-picker__option">
<div class="variant-picker__option-info">
{%- if hide_size_chart != true and block.settings.size_chart_page != blank and size_label_list contains option_downcase -%}
{%- capture drawer_id -%}size-chart-{{ option.position }}-{{ form_id }}{%- endcapture -%}
<button type="button" class="text-sm text-subdued" aria-controls="{{ drawer_id | escape }}" aria-expanded="false">
<span class="link">{{ 'product.general.size_chart' | t }}</span>
</button>
<x-drawer id="{{ drawer_id }}" class="drawer drawer--lg">
<span class="h5" slot="header">{{ block.settings.size_chart_page.title }}</span>
<div class="prose">
{{- block.settings.size_chart_page.content -}}
</div>
</x-drawer>
{%- endif -%}
</div>
{%- if resolved_option_selector_style == 'dropdown' -%}
{%- capture variant_drawer_id -%}variant-picker-drawer-{{ section.id }}-{{ product.id }}-{{ option.position }}{%- endcapture -%}
{%- assign param_name = form_id | append: '-option' | append: option.position -%}
{% comment %} Drawer Trigger Button {% endcomment %}
<button type="button" class="button button--xl button--primary button--full" aria-controls="{{ variant_drawer_id }}" aria-expanded="false">
{{ option.name }} {{ 'product.general.select' | t }}
</button>
{% comment %} Drawer Definition {% endcomment %}
<x-drawer id="{{ variant_drawer_id }}" class="drawer drawer--variant-picker drawer--lg" anchor="bottom">
{% comment %} Drawer Header {% endcomment %}
<div slot="header" class="h-stack items-center justify-between">
<p class="h5">{{ option.name | upcase }} {{ 'product.general.select' | t | upcase }}</p>
</div>
{% comment %} Drawer Main Content {% endcomment %}
<div>
<div class="variant-grid__shipping-info">
<span class="variant-grid__shipping-dot"></span>
<span class="shipping-text">{{ 'product.general.shipping_in_48h' | t }}</span>
<span class="variant-grid__shipping-badge">48H</span>
</div>
<div data-option-selector class="variant-grid">
{%- for option_value in option.values -%}
{%- if hide_sold_out_variants == false or option_value.available or option_value.selected -%}
{% comment %} We use labels/radios even if update_url is true for consistent behavior in drawer {% endcomment %}
<label
class="variant-grid__option {% unless option_value.available %}is-disabled{% endunless %} {% if option_value.selected %}is-selected{% endif %}"
for="{{ param_name }}-{{ option_value.id | escape }}"
onclick="selectVariant('{{ option_value.id }}', this, event);"
>
<input class="sr-only" type="radio" id="{{ param_name }}-{{ option_value.id | escape }}" form="{{ form_id }}" name="{{ param_name }}" data-option-position="{{ option.position }}" value="{{ option_value.id }}" {% if option_value.selected %}checked{% endif %} {% unless option_value.available %}disabled{% endunless %}>
{% if fast_shipping_values contains option_value.name %}
<span class="variant-grid__option-available">
48H
</span>
{% endif %}
<input class="sr-only" type="radio" id="{{ param_name }}-{{ option_value.id | escape }}" form="{{ form_id }}" name="{{ param_name }}" data-option-position="{{ option.position }}" value="{{ option_value.id }}" {% if option_value.selected %}checked{% endif %} {% unless option_value.available %}disabled{% endunless %}>
<span class="variant-grid__option-value">{{ option_value.name | remove: " - Sofort verfügbar!" }}</span>
<span class="variant-grid__option-price">{{ option_value.variant.price | money }}</span>
</label>
{%- endif -%}
{%- endfor -%}
</div>
</div>
{% comment %} Drawer Footer {% endcomment %}
<div slot="footer">
{%- assign drawer_form_id = form_id | append: '-drawer' -%}
{%- form 'product', product, id: drawer_form_id -%}
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
<button type="button" class="button button--xl button--secondary button--full"
onclick="
const form = document.getElementById('{{ drawer_form_id }}');
const drawer = this.closest('x-drawer');
const formData = new FormData(form);
fetch('/cart/add.js', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
// Update cart count or show success message if needed
if (drawer) {
drawer.dispatchEvent(new CustomEvent('dialog:force-close', { bubbles: true }));
}
// Trigger cart drawer to open or update cart
document.documentElement.dispatchEvent(new CustomEvent('cart:refresh', { bubbles: true }));
// Open cart drawer
const cartDrawer = document.getElementById('cart-drawer');
if (cartDrawer) {
setTimeout(() => {
cartDrawer.setAttribute('open', '');
cartDrawer.dispatchEvent(new CustomEvent('dialog:open', { bubbles: true }));
}, 300);
}
})
.catch(error => console.error('Error:', error));
return false;
">
{%- if product.selected_or_first_available_variant.available -%}
{{ 'product.general.add_to_cart_button' | t }}
{%- else -%}
{{ 'product.general.sold_out_button' | t }}
{%- endif -%}
</button>
{%- endform -%}
</div>
</x-drawer>
{%- else -%}
<div {% unless block.settings.stack_blocks %}class="scroll-area bleed sm:unbleed"{% endunless %}>
<div class="variant-picker__option-values {% if block.settings.stack_blocks %}wrap{% else %}scroll-area bleed sm:unbleed{% endif %} {% if resolved_option_selector_style == 'swatch' and settings.color_swatch_style == 'rectangle' %}variant-picker__option-values--color gap-4{% else %}gap-2{% endif %}">
{% liquid
assign name = form_id | append: '-option' | append: option.position
for option_value in option.values
case resolved_option_selector_style
when 'variant_image'
render 'option-value', type: 'thumbnail', form: form_id, option_value: option_value, param_name: name, option_position: option.position, hide_if_disabled: hide_sold_out_variants, reload_page_for_combined_products: update_url, id_prefix: forloop.index, bordered: true
when 'swatch'
render 'option-value', type: 'swatch', form: form_id, option_value: option_value, param_name: name, option_position: option.position, hide_if_disabled: hide_sold_out_variants, reload_page_for_combined_products: update_url, id_prefix: forloop.index
when 'block'
render 'option-value', type: 'block', form: form_id, option_value: option_value, param_name: name, option_position: option.position, hide_if_disabled: hide_sold_out_variants, reload_page_for_combined_products: update_url, id_prefix: forloop.index
when 'block_swatch'
render 'option-value', type: 'block', form: form_id, option_value: option_value, param_name: name, option_position: option.position, show_swatch: true, hide_if_disabled: hide_sold_out_variants, reload_page_for_combined_products: update_url, id_prefix: forloop.index
endcase
endfor
%}
</div>
</div>
{%- endif -%}
</fieldset>
{%- endfor -%}
</variant-picker>
{%- endunless -%}
