Shopify Themeで同系色の商品をカラーチップで絞り込む方法案

フロントエンドエンジニアの氏家です。

私は現在、アウトドア用品のセレクトショップ「hinataストア」の運用を行うストアチームで開発をしています。

hinatastore.jp

hinataストアは Shopify で構築されており、フロントエンドは Shopify Theme を使用しています。

最近、hinataストアで取り扱う商品が増えてきており、連れ各コレクションに表示する商品数が多くなってしまっています。 そのため、目当ての商品をすぐに見つけられなくなってきました。

そこで、コレクションページに商品の絞り込み機能を追加し、「カラーチップを押すとその色と同系色の商品が絞り込まれる」ためのフィルターを作成しました。

今回はこのフィルターがどのように作られ、どのように機能しているかについて簡単に説明していきたいと思います。なお、マーチャントごとに使用しているテーマが違うと思いますので、あくまで「こうすればできるよ」程度の説明と簡単なコード例のみに留めます。

実際の動き

前提

前提として以下を満たしている必要があります。

  • Shopify Themeの商品絞り込みは、Online Store 2.0 のテーマでのみ使用することができる ( hinataストアでは有料の日本語テーマ「MISEル」を使用しているが、他のテーマでも同様に実装が可能 )

  • Liquid コードの修正や JavaScript のDOM操作についてある程度の知識を有している

Shopifyにはコレクションの商品を絞り込むための機能(詳しくは後述)があります。

例えば、「カラー」というバリエーションの「RED」という名前で登録された商品を取得したい場合、クエリに filter.v.option.カラー=RED というパラメータを設定する、といった感じです。

しかし、例えば「○○RED」「△△RED」のように同じ赤色でもバリエーション名が異なる場合があります。hinataストアのようなセレクトショップだとバリエーション名は商品本来の名前で登録する必要があるため、バリエーション名を「RED」で統一することはできません。

実装案

前提を踏まえると、赤色のカラーチップで絞り込みたい場合、「○○RED」「△△RED」という同じ赤色のカラーチップをそれぞれ用意して選択する必要があります。

つまり、同系色の商品を1度に絞り込みたい場合は、該当するバリエーション名を複数同時に検索できれば良いというわけです。

上記の方法を実現するために必要な設定は以下のとおりです。

  1. 各商品へのバリエーション設定 (「カラー」などのバリエーション名で色名を登録)
  2. コレクションページに Storefront filtering のフォーム設置
  3. バリエーション名、カラーコード、カラーチップの対応付け (セクションなどを用意して、バリエーション名とそれに対応するカラーコードを対応付ける)
  4. 3の対応付けをコード側に反映 (セクションに設定された値を取得して、フィルターの処理で使用できるようにする)
  5. 絞り込み用カラーチップの挙動を修正

それぞれの設定について順番に見ていきたいと思います。

1. 各商品へのバリエーション設定

まずは絞り込み条件となるバリエーションの設定を行います。 バリエーション名は何でも良いですが全ての商品で統一する必要があるので、今回は「カラー」という名前で設定したいと思います。

カラーバリエーション

2. コレクションページに Storefront filtering のフォーム設置

絞り込み用のフォームを設置して、色ごとに商品を絞り込めるようにします。 実装方法についてはテーマごとに異なると思いますが、以下が参考になります。

Shopify Search & Discoveryアプリ を追加して管理画面上で設定するのが1番簡単な方法だと思いますが、カラーチップによる絞り込みを実装する場合はさらに追加の実装が必要です。

hinataストアの絞り込みフォームは画像のようになっています。

hinataストア絞り込みフォーム

3. バリエーション名、カラーコード、カラーチップの対応付け

2で設定したバリエーションと、そのバリエーションを絞り込むための色(カラーコード)を対応付けます。 商品のタグやメタフィールドに設定するやり方、Liquidコードにベタ書きで対応付けするやり方などがあると思いますが、今回はMISEルに標準で備わっている、セクションで対応付けするやり方を紹介します。

セクションの作成

1.まずTOPもしくはコレクションページにセクションを作成

2.スキーマにバリエーション名とカラーコードを対応付けるブロックを設定

スキーマの例

{
  "type": "text",
  "id": "property_value_1",
  "label": { "en": "Property & Value #1", "ja": "プロパティと値 #1"}
},
{
  "type": "color",
  "id": "color_1",
  "label": { "en": "Color #1", "ja": "カラー #1"}
},
{
  "type": "text",
  "id": "property_value_2",
  "label": { "en": "Property & Value #2", "ja": "プロパティと値 #2"}
},
{
  "type": "color",
  "id": "color_2",
  "label": { "en": "Color #2", "ja": "カラー #2"}
},
....(大体10個くらい)

3.スキーマにカラーチップとカラーコードを紐付けるブロックを設定 ↓スキーマの例

{
  "type": "text",
  "id": "color_name_1",
  "label": { "en": "Color Name #1", "ja": "色の名前 #1"},
},
{
  "type": "color",
  "id": "color_value_1",
  "label": { "en": "Color #1", "ja": "カラー #1"}
},
{
  "type": "text",
  "id": "color_name_2",
  "label": { "en": "Color Name #2", "ja": "色の名前 #2"},
},
{
  "type": "color",
  "id": "color_value_2",
  "label": { "en": "Color #2", "ja": "カラー #2"}
},
....(大体10個くらい)

4.テーマ編集画面でそれぞれのブロックに値を入れる

↓2のブロック

↓3のブロック

これで カラーチップが押されるそのカラーチップに紐付いたカラーコードが選択されるカラーコードに紐付いたバリエーションが選択される という対応付けが完了したことになります。

※そもそも↓画像のようにバリエーションをカラーチップで表示する必要がない場合は、2で対応付けるのはカラーコードである必要はありません。

4. 3の対応付けをコード側に反映

セクションで設定された値は setting_data.json に格納され、sectionファイル内で参照することができます。 しかし全ての処理をsection内に書くわけにはいかないので別ファイルのLiquidに値を渡したいのですが、別セクションの値を参照したり値を渡したりする機能はありません。 なので、今回はグローバル変数に格納して各ファイルが参照するかたちで設定してみます。

↓ コード例( block.type によって処理を分ける必要がありますがここでは省略しています )

// 明示的に空の配列を定義
window.customColorArray = [];
window.colorChipArray = [];
{%- for block in section.blocks -%}

    // バリエーション用のカラーチップ
    {%- for i in (1..10) -%}
        {%- assign prop_property_value_name = 'property_value_' | append: i -%}
        {%- assign prop_color_name = 'color_' | append: i -%}
        {%- if block.settings[prop_property_value_name] != blank and block.settings[prop_color_name] != blank -%}
            {%- assign property_value_split = block.settings[prop_property_value_name] | split: '|' -%}
            {%- assign value = property_value_split[1] -%}
            {%- assign color = block.settings[prop_color_name] -%}
            {%- if value != blank and color != blank  -%}
                customColorArray.push({
                    name: "{{ value }}",
                    color: "{{ color }}",
                });
            {%- endif -%}
        {%- endif -%}
    {%- endfor -%}

    // 絞り込み用のカラーチップ
    {%- for i in (1..10) -%}
        {%- assign prop_color_name_key = 'color_name_' | append: i -%}
        {%- assign prop_color_value_key = 'color_value_' | append: i -%}
        {%- if block.settings[prop_color_name_key] != blank and block.settings[prop_color_value_key] != blank -%}
            {%- assign color_name = block.settings[prop_color_name_key] -%}
            {%- assign color_value = block.settings[prop_color_value_key] -%}
            {%- if color_name != blank and color_value != blank  -%}
                colorChipArray.push({
                    "{{ color_name }}": "{{ color_value }}",
                });
            {%- endif -%}
        {%- endif -%}
    {%- endfor -%}
{%- endfor -%}

5. 絞り込み用カラーチップの挙動を修正

あとは colorChipArray をもとに絞り込み用のフォームを設置し、各フィルターが選択されたときの挙動を修正するだけです。

↓コード例 ( 各テーマの仕様に合わせて修正する必要があります )

onSubmitHandler(event) {
  const value = event.target.getAttribute("value");
  const targetColors = window.colorChipArray
    .map((colorChip) => {
      return colorChip[value];
    })
    .filter((e) => e);

  // 受け取った値が色名で、かつスキーマに登録されているものだった場合
  if (targetColors.length) {
    // 該当するカラーコードの個数分実行する
    targetColors.forEach((targetColor) => {
      if (targetColor) {
        const colorNameArray = window.customColorArray
          .map((customColor) => {
            if (targetColor === customColor.color) return customColor.name;
          })
          .filter((e) => e);

        // 非表示になっているカラーチップを全て取得するためのセレクタ
        const selectors = colorNameArray.length
          ? colorNameArray
              .map((colorName) => {
                return `input[value='${colorName}']`;
              })
              .join(", ")
          : null;

        if (event.target.checked) {
          // 該当するカラーチップ全てにチェックを入れる
          if (selectors) {
            const originalColorChipElements =
              document.querySelectorAll(selectors);
            originalColorChipElements.forEach((element) => {
              element.checked = true;
            });
          }
        } else {
          // 該当するカラーチップ全てのチェックを外す
          if (selectors) {
            const originalColorChipElements =
              document.querySelectorAll(selectors);
            originalColorChipElements.forEach((element) => {
              element.checked = false;
            });
          }
        }
      }
    });
  }
  // あとはクエリを作成して再レンダリングするかページリロードする
  ...
}

この実装例は、window.customColorArray の値で作成された非表示のフィルターを動的に選択/解除することで、あたかも1つのカラーチップが押されたことによって複数のバリエーションが選択されているように振る舞います。 (使用しているテーマに破壊的変更を加えずに実装しようとしたためこのような実装になっています。)

他にも、クエリをもとにフィルターのチェックを保持しておく処理が必要になりますが、値が出揃っている関係でそこまで複雑な実装にはならないと思います。

まとめ

Shopify は開発のためのリソースが豊富ですが、それ故に何をどのように使っていいか分からなくなる場面が多くあります。 今回のケースも、Storefront APIを叩いたり、Shopifyアプリを作ったりすることで実装することもできたと思いますが、あくまで既存テーマコードの変更のみで対応するやり方を採用してみました。

Shopify開発のナレッジが少ないためこれが良い実装なのかどうかは分かりませんが、同じような機能の実装を検討している方の参考になればと思います。

vivit では新しいことにチャレンジし、ともにプロダクトを成長させていけるエンジニアを募集中です!

少しでも興味を持って頂いた方は、是非カジュアル面談にお越しください。

新卒採用

www.wantedly.com

キャリア採用

www.wantedly.com

www.wantedly.com

www.wantedly.com

皆様とお話できるのを楽しみにしております!!