Shopifyでサイドメニューのナビゲーションを自作する

Shopifyでサイドメニューのナビゲーションを自作する

Shopifyは海外発のプラットフォームであるため、日本の商慣習に合わない、テーマデザインが日本向けでない、などのデメリットが挙げられることがあります。
とくに、昨今のECサイトデザインではシンプルな1カラムでファセットなどのツールバーがサイドにないケースも多いです。

ところが、日本のユーザーにとっては今でも楽天などのECサイトのスタイルが主流で、web制作においてはクライアントからも、左や右にナビゲーションなどが設置されたサイドメニューがほしいという要望があがったりします。

公式テーマの中でも、有料のテーマならサイドメニューがあるスタイルを選択することができるものもいくつか存在します。
しかし、制作しようとしているサイトに合ったテーマを探すのも手間ですし、とくに、取り扱っている商品のジャンルや商品数が膨大なサイトの場合、無料テーマや、または有料テーマのデフォルトのカスタマイズ項目のみで手軽に始めるには限界があったりします。

というわけで今回は、Dawnテーマをベースに、サイドメニューを設置して階層化されたナビゲーション(リンクリスト)を設置する方法をご紹介します。

2カラムレイアウトへの変更

theme.liquid

まずは2カラムレイアウトにするための記述をしていきます。
Dawnテーマの場合、theme.liquidファイルの中に、main要素を記述している箇所があります。


<main
      id="MainContent"
      class="content-for-layout focus-none"
      role="main"
      tabindex="-1">
      {{ content_for_layout }}
    </main>

このメイン要素を囲うラッパーを用意します。

    {% liquid
    
    if template contains 'blog' or template contains 'page' or template contains 'collection-list'

    assign col_layout = true

    endif

    %}

    <div class="wrapper">
      <main
        id="MainContent"
        class="content-for-layout focus-none col-sp-12{% if col_layout == true %} col-8{% else %} col-12{% endif %}"
        role="main"
        tabindex="-1">
        {{ content_for_layout }}
      </main>
      {%- if col_layout == true -%}
        <aside class="side-menu sp-hide col-4">
          ここに左メニュー
        </aside>
      {%- endif -%}
    </div>

例ではmain要素との対比でわかりやすくaside要素を使用していますが、カラムの内容に応じてnavやsectionなど自由に使えますので、それぞれ適切にマークアップしてください。

ラッパーのレイアウト用CSS

ラッパーのHTML要素を用意したら、CSSでカラムレイアウトにします。
Dawnテーマの場合、モバイル端末のサイズでは、ドロワーのメニューが別のコードで用意されているため、この左メニューのコードをそのままモバイル用にする必要がありません。
モバイル端末では自分で追加した左メニューを非表示にします。

.wrapper {
  display: flex;
  align-items: stretch;
  width: 80dvw;
  margin: 40px auto;
}

.col-4 {
  flex-basis: 33.33%;
  max-width: 33.33%;
}

.col-8 {
  flex-basis: 66.66%;
  max-width: 66.66%;
}

.col-12 {
  flex-basis: 100%;
  max-width: 100%;
}

@media (max-width: 992px) {

  .sp-hide {
    display: none;
  }

  .col-sp-4 {
    flex-basis: 33.33%;
    max-width: 33.33%;
  }
  
  .col-sp-6 {
    flex-basis: 50%;
    max-width: 50%;
  }
  
  .col-sp-8 {
    flex-basis: 66.66%;
    max-width: 66.66%;
  }
  
  .col-sp-12 {
    flex-basis: 100%;
    max-width: 100%;
  }

}

.wrapper main {
  order: 2;
}

.wrapper .side-menu {
  order: 1;
  background: var(--gradient-base-background-2);
}

HTML側ではmain要素よりもaside要素を後に書いていますが、flexレイアウトの場合orderプロパティを使うことで、HTML要素の記述順をそのままに、表示順序を変更することが可能です。
このあたりのCSSの組み方はサイトのレイアウトや文書構造に応じて自由にやってください。
(Dawnテーマに用意されているgridgrid__itemなどのclassをそのまま使っても構いません)

なんでわざわざ上記のような組み方をしたかというと、グローバルナビとサイドメニューで同じメニューが重複してしまうため、「文書内での重要度があまり高くなく、main要素のコンテンツとも直接関係ない」サイドメニューのナビゲーションをaside要素にしてコードの記述も後のほうにした、という理由がなくもないです。

サンプルのようにグローバルナビとサイドメニュー(本来ならローカルナビ)が同じメニューな時点でサイト設計としてどうなの感もありますので、実務ではさらにカスタマイズした方が良いでしょう。

ちなみに私が仕事で作ったサイトでは、もともとあったグローバルナビと製品のメニューを別で用意し、ドロワーでは製品情報のメニューを目立たせ、もともとのグローバルナビ(ホームやお問い合わせフォーム)を下の方に表示しています。

できたのがこんな感じです。

サイドメニューをスニペット化する

サイドメニューのコードがすべてtheme.liquidに入っていると、運用中に修正作業などが手間になります。
snippetsディレクトリにside-menu.liquidを作成し、renderタグで呼び出すことにします。

      <aside class="side-menu sp-hide col-4">
        {%- render 'side-menu' -%}
      </aside>

サイドメニューのデータを作る

あとはside-menu.liquidの中身を好きに書いてもらえればOKです。
が、今回は「商品のジャンルや商品数が膨大なサイト」を想定して、ナビゲーションのリンクリストを作りたいと思います。

できあがりのイメージ

以前の記事でも使ったサンプルの、ゲームソフトを販売するECサイトを例にします。
「ゲームのジャンル」「ソフトの対応ハード」「メーカー」といった分類が想定されますね。

画像の左側はそれぞれの分類別のリスト、右側は分類を3階層にした場合のリストです。(実際はゲームの分類をこういう階層にはしないでしょうが、あくまでも設置の例として参考にしてください)

ちなみに、ゲームソフトの商品データはNotion AIを使用して作成しました!

これらの商品登録と、各カテゴリを条件にした自動コレクションの用意を済ませておきます。

Shopifyの管理画面でメニューを作成する

管理画面の「販売チャネル」>「オンラインストア」>「メニュー」で、それぞれのメニューを作成します。
事前に作成したコレクションを選択してリストを作っていき、そのメニューのハンドルも命名しておきます。

このメニューは3階層まで階層構造にすることができるので、階層のあるリストも作りました。

準備ができたら、いよいよこのデータをサイドメニューに表示していきます。

side-menu.liquidのコード

LiquidやShopifyAPIの仕様については公式ドキュメントが参考になります。

これらの情報をもとにして、さきほど作成したスニペットファイルに、下記のようなコードを書きます。
やってることは、linklistsオブジェクトでメニューのハンドルを指定し、そのアイテムをforタグでループしていくだけです。

<nav class="side-menu">
  <dl>
    <dt>メーカーから探す</dt>
    <dd>
      <ul class="menu">
        {% assign menu = linklists.game-vendor %}
        {% for link in menu.links %}
          <li>
            <a href="{{ link.url }}">{{ link.title }}</a>
          </li>
        {% endfor %}
      </ul>
    </dd>
  </dl>
  <dl>
    <dt>ジャンルで探す</dt>
    <dd>
      <ul class="menu">
        {% assign menu = linklists.game-category %}
        {% for link in menu.links %}
          <li>
            <a href="{{ link.url }}">{{ link.title }}</a>
          </li>
        {% endfor %}
      </ul>
    </dd>
  </dl>
  <dl>
    <dt>対応機種で探す</dt>
    <dd>
      <ul class="menu">
        {% assign menu = linklists.game-hardware %}
        {% for link in menu.links %}
          <li>
            <a href="{{ link.url }}">{{ link.title }}</a>
          </li>
        {% endfor %}
      </ul>
    </dd>
  </dl>
  <dl>
    <dt>3階層メニュー</dt>
    <dd>
      <ul class="menu">
        {% assign menu = linklists.directories-menu %}
        {% for link in menu.links %}
          <li>
            <a href="{{ link.url }}">{{ link.title }}</a>
            {% if link.links.size > 0 %}
              <ul>
                {% for directory2 in link.links %}
                  <li>
                    <a href="{{ directory2.url }}">{{ directory2.title }}</a>
                    {% if directory2.links.size > 0 %}
                      <ul>
                        {% for directory3 in directory2.links %}
                          <li>
                            <a href="{{ directory3.url }}">{{ directory3.title }}</a>
                          </li>
                        {% endfor %}
                      </ul>
                    {% endif %}
                  </li>
                {% endfor %}
              </ul>
            {% endif %}
          </li>
        {% endfor %}
      </ul>
    </dd>
  </dl>
</nav>

3階層メニューの場合は、ネストが必要になります。
ちなみに、上記の3階層バージョンのコードでは、下層のリストアイテムがあるかどうかを判定しループするようになっているので、すべてのメニューで流用することができます。
たとえばメニューのハンドルを配列の値に入れ、linklistsオブジェクトにハンドルを渡しながらループするようにすれば、ひとまとめにすることもできます。

リンクリストのCSSサンプル

.side-menu dl {
  padding: 1em;
}

.side-menu dd {
  margin: 0;
  padding: 0;
}

.side-menu .menu li {
  margin: 1em 0 0 2em;
}

.side-menu .menu li::before {
  -webkit-font-smoothing: antialiased;
  content: "\f054";
  font: var(--fa-font-solid);
  margin-right: 0.6em;
}

アイコンはFont Awesomeを使用しています。
ShopifyでFont Awesomeを導入する方法は先日ブログを書きましたので、よろしければあわせてご覧ください。

これで最初にお見せした画像のようなリンクリストのできあがりです。

特定のコンテンツのときだけサイドメニューを表示したい

トップページではサイドメニューは邪魔だ!とか、コレクションではファセットをサイドメニューにしたいから自前のサイドメニューはいらない!とか、あると思います。
たとえばページやブログ、コレクション一覧など特定のコンテンツに滞在しているときだけ、サイドメニューから商品を探せるようにしたい!という場合は、theme.liquidで分岐すればOKです。
サンプルのコードでは冒頭に条件判定をして変数を定義しています。

    {% liquid
    
    if template contains 'blog' or template contains 'page' or template contains 'collection-list'

    assign col_layout = true

    endif

    %}

... 略 ...

      {%- if col_layout == true -%}
        <aside class="side-menu sp-hide col-4">
          ここに左メニュー
        </aside>
      {%- endif -%}

unlessを使って、トップページ以外、という条件のつけかたも可能です。

上記はサイドメニューをまるごとレンダリングしないようにしていますが、サイドメニューの中身のスニペットファイルを複数用意して切り替えることもできます。

<aside class="side-menu sp-hide col-4">
{%- if col_layout == true -%}
    {%- render 'side-menu' -%}
{%- else -%}
    {%- render 'side-menu-2' -%}
{%- endif -%}
</aside>

作ったメニューをモバイルのドロワーでも表示できるようにする

今回作ったサイドメニューはモバイルでは非表示にしました。
その理由として、もともとDawnテーマがモバイル用のドロワーを用意しており、ヘッダに設定されているグローバルナビをそのままドロワーとして表示してくれるからです。
しかし、何もカスタマイズしないと、ホームやお問い合わせのリンクが表示されるのみです。
このままではモバイルの場合に製品情報を表示することができません。

そこで、テーマのカスタマイズ画面で、先ほど作ったメニューを設定したいと思います。

カスタマイズ画面を開くと最初に「ホームページ」のテンプレートが表示されています。

このテンプレート上で、ヘッダ部分をクリックすると、そのセクションのカスタマイズ項目が表示されます。

ここでメニューを任意のメニューに変更すれば、グローバルナビの中身が切り替わります。

上記の例では、製品情報のリストのみのメニューを設定しているので、ホームやお問い合わせへのリンクなどがなくなってしまいましたが、メニューにグローバルナビに表示したいリンクをすべて入れておけば大丈夫です。

まとめ

というわけで、シャレオツ海外仕様のデザインは求めていない!とにかくサイドメニューがほしい!というプロダクト向けのカスタマイズ解説でした。
Shopifyのカテゴリ階層のない仕様は、WordPressなどのCMSに慣れている人だと違和感があるかもしれませんね。

IT業界にいると認識が薄れがちですが、世の中にはフリーワード検索で検索することができない、する気がない人というのが、私たちが思っているよりも大勢います。
それはタイピングができないからであったり、うまく検索できなかったという過去の経験から、メニューから探すという習慣が染み付いているケースもあったり。
クライアントのターゲット層によっては、こういったメニューを用意してあげたほうがスムーズにお買い物ができる場合もありますので、作れるようになっておいて損はないと思います。