2023.08.22[Tue]

Shopifyの商品詳細ページにおいて選択されているバリエーションの商品画像だけを表示するコードカスタマイズ

  • Shopify

目次

  • - はじめに
  • - 実装コンセプト
  • - 前提
  • - コンセプト
  • - コードカスタマイズ
  • - 結果
  • - まとめ

こんにちは。
ディレクターの田辺です。

ECサイトを構築するとき、下記のようにされたいことはないでしょうか?

“商品詳細ページで、選択されているバリエーションの商品画像だけを表示したい”

例えば、アパレル商品で、白と黒の2つのカラーバリエーションがある場合に、商品詳細ページにて、白を選択しているときには白の商品画像だけを、黒を選択しているときには黒の商品画像だけを表示させたい、というご要望は多いのではないかと思います。

しかし、ShopifyのデフォルトテーマDawnにはこの機能がありません。

アプリ導入や、有料のテーマを購入することで実装できますが、できればお金をかけずに解決したいのが人情です。

そこで今回は、コードカスタマイズだけで「商品詳細ページにて、選択されているバリエーションの商品画像だけを表示する」機能を実装する方法をご紹介します。

各バリエーションに商品画像が1枚ずつしか存在しない場合はこのコードカスタマイズは不要です

Dawn 11.0.0では、各バリエーションに商品画像が1枚ずつしか設定されていない場合はコードカスタマイズを行わなくても、選択中のバリエーションの商品画像のみを表示することが可能です。各バリエーションに商品画像が複数枚つづ存在する場合のみ、本記事で紹介するコードカスタマイズをご活用ください。

はじめに

今回のコードカスタマイズは、Dawn 11.0.0を前提にしています。
Dawnのバージョンが異なる場合、うまく動かない可能性があることご承知おきください。

また、全ての状況において動作するような汎用的なコードカスタマイズでもありません。
実装コンセプトに、本記事で紹介するコードカスタマイズがうまく動作するであろう前提を記載しますので、ご一読ください。

実装コンセプト

前提

  • 画像だけが対象です。動画の場合の動作検証はしていません
  • アパレルでよくある「色」と「サイズ」の2つのオプションを持つ商品が対象です
  • 「色」と「サイズ」のうち「色」にバリエーションごとの商品画像を紐付けます
  • 「色」と「サイズ」のうち「色」がOption1であるとします
  • ストア管理画面 > オンラインストア > テーマ > カスタマイズ において 商品 > デフォルトの商品 の セクション「商品情報」 > バリエーションを選択した後、他のバリエーションのメディアを非表示にする が有効であるとします(下図参照)

コンセプト

  • 商品画像の並び順と、バリエーションことのサムネイル画像によって、どの画像がどのバリエーションのものかを判定します
  • 商品画像は、バリエーションの「色」の順番で並べます。例えば、色の並びがBlack, Green, Brownであり、それぞれに前面と背面の2種類の画像がある場合、商品画像の並びは、Black前面、Black背面、Green前面、Green背面、Brown前面、Brown背面 にします。
  • この時、バリエーションに設定するサムネイル画像は、例えば、サイズがSMLの3展開の場合は、Black/S、Black/M、Black/Lには、Black前面の画像を設定します。同様に、Green/S、Green/M、Green/Lには、Green前面を設定、Brown/S、Brown/M、Brown/Lには、Brown前面 を設定します。

このコンセプトを、実際の商品管理に反映すると下図のようになります。

コードカスタマイズ

可能な限り、既存のコードを変更せずに、コードを追加するだけで済むようにします。

まず、スニペット(snippets) > product-media-gallery.liquid を開きます。
そして、下記のコードを45行目付近に追加します。


{%- liquid 
   assign variant_featured_media_ids = ""
   assign variant_featured_media_key_ids = ""
   for variant in product.variants
     assign variant_featured_media_ids = variant_featured_media_ids | append: variant.featured_media.id | append: ','
   endfor
   assign variant_featured_media_ids = variant_featured_media_ids | split: ','
   assign variant_featured_media_key_ids = variant_featured_media_ids | uniq

   assign product_media_ids = ""
   for media in product.media
     assign product_media_ids = product_media_ids | append: media.id | append: ','
   endfor
   assign product_media_ids = product_media_ids | split: ','

   assign current_variant_featured_media_id = product.selected_or_first_available_variant.featured_media.id
   assign next_cariant_featured_media_key_id = variant_featured_media_key_ids | join: ',' | split: current_variant_featured_media_id | last | remove_first: ',' | split: ',' | first
   assign current_variant_media_ids = product_media_ids | join: ',' | split: current_variant_featured_media_id | last | remove_first:',' | split: next_cariant_featured_media_key_id | first | remove_last:',' | prepend: ',' | prepend: current_variant_featured_media_id | split: ','
-%}

<script type="application/json" id="variant_featured_media_key_ids">
{{ variant_featured_media_key_ids | json }}
</script>
<script type="application/json" id="product_media_ids">
{{ product_media_ids | json }}
</script>

<script defer>
  window.addEventListener('load', function() { 
    const variantRadios = document.querySelector('variant-radios');
    const variantRadiosInputs = variantRadios.querySelector('fieldset').querySelectorAll('input');
    const sliderComponetLis = document.querySelector('slider-component').querySelectorAll('li');
  
    const variantFeaturedMediaKeyIds = JSON.parse(document.getElementById('variant_featured_media_key_ids').textContent);
    const productMediaIds = JSON.parse(document.getElementById('product_media_ids').textContent);

    const variantData = variantRadios.getVariantData();
  
    variantRadiosInputs.forEach(input => {
      input.addEventListener('change', switchImages);
    })
  
    function switchImages(e) {
      const selectedVariant = variantData.find(variant => variant.option1 == e.target.value);
      const selectedVariantFeaturedImageId = String(selectedVariant.featured_media.id);
      const nextVariantFeaturedImageId = variantFeaturedMediaKeyIds[variantFeaturedMediaKeyIds.indexOf(selectedVariantFeaturedImageId) + 1];
      const startIndex = productMediaIds.indexOf(selectedVariantFeaturedImageId)
      const endIndex = nextVariantFeaturedImageId != undefined ?  productMediaIds.indexOf(nextVariantFeaturedImageId) :  productMediaIds.length;
      const selectedVariantImageIds =  productMediaIds.slice(startIndex, endIndex);
      sliderComponetLis.forEach(li => {
        const mediaId = li.dataset.mediaId.split('-').pop();
        if (selectedVariantImageIds.indexOf(mediaId) > -1) {
          li.classList.remove('adjust-to-variant'); 
        } else {
          if(!li.classList.contains('adjust-to-variant')) {
            li.classList.add('adjust-to-variant');
          }
        }
      });
    }
  });
</script>

<style>
  .adjust-to-variant {
    display: none;
  }
</style>

そして、
スニペット(snippets) > product-media-gallery.liquid の164行目付近にあるコードを下記のように変更します。(元コードは95行目付近にありますが、上記のコードを追加したことで164行目あたりまで行数がずれます。)

class="product__media-item grid__item slider__slide{% if single_media_visible %} product__media-item--single{% endif %}{% if product.selected_or_first_available_variant.featured_media == null and forloop.index == 1 %} is-active{% endif %}{% if media.media_type != 'image' %} product__media-item--full{% endif %}{% if section.settings.hide_variants and variant_images contains media.src %} product__media-item--variant{% endif %}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--fade-in{% endif %}"


class="product__media-item grid__item slider__slide{% if single_media_visible %} product__media-item--single{% endif %}{% if product.selected_or_first_available_variant.featured_media == null and forloop.index == 1 %} is-active{% endif %}{% if media.media_type != 'image' %} product__media-item--full{% endif %}{% if section.settings.hide_variants and variant_images contains media.src %} product__media-item--variant{% endif %}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--fade-in{% endif %}{% assign media_id_string = media.id | append: '' %}{% unless current_variant_media_ids contains media_id_string %} adjust-to-variant{% endunless %}"

結果

下図のように、商品詳細ページにおいて選択されているバリエーションの商品画像だけが表示されます。

例では、各バリエーションごとに商品画像を2枚づつしか設定していませんが、3枚以上であってもうまく動作するはずです。

まとめ

今回、ご紹介したコードカスタマイズは、「限られた状況でのみ動作する」ものです。
また、十分な動作検証ができているとも言えません。

下記のような場合はぜひお問い合わせください。

  • 自分の環境だとXXXやYYYという条件があり、この方法だとうまく動作しない。環境に合わせたコードカスタマイズを教えてほしい
  • 単純にバグがあり、うまく動かない。動くようにしてほしい

お問い合わせフォームはこちらです。

追加カスタマイズについても、ある程度までであれば無料で相談に乗らせていただきます。

それでは。

Share

Shopify theme app extensionのためのNode.js + Ruby DockerイメージShopify FlowのGet customer dataやGet order dataのクエリの書き方