Shopifyでオリジナルの動的セクションを作ろう!

プログラミングイメージ
石田正泰
この記事を書いた人 石田正泰

中学生の時に趣味でZ80マシン語やFortran等を始めてから、現在まで数多くのプログラミング言語を経験。ShopifyによるECサイト構築では主にカスタマイズを担当。

はじめに

Shopify Unite 2021での大幅なアップデートによりSection Everywhere(どこでもセクション)が実装され、それに伴いテンプレートは主にJSON形式で記述するようになりました。JSONテンプレートに対応したテーマをShopifyはOnline Store 2.0(以下、OS2.0)対応テーマと呼んでいます。

全ページ共通のヘッダーとフッターは、今も昔も静的セクションを作ってレイアウトに直接設置する場合が多いと思いますが、OS2.0に対応したテーマの場合にはセクションを動的セクションで作り各ページ用のJSONテンプレートに設置する場合が圧倒的に増えました。

しかし情報はまだまだ少なく、動的セクションの作り方に悩まれる方も多いと思います。
情報はあればあるほどよいと思いますので、この記事では動的セクション、特にschema(構造)タグの書き方を中心に、私流のやり方で少しずつ作りながら解説します。

 

「Swiper.js」を使ったスライドショーを作成

デフォルトのOS2.0無料テーマ「Dawn」には何故かスライダーがありません。
そこでここでは例として、Swiper.jsを使用したスライダーセクションを作ろうと思います。

Swiper.jsとは、レスポンシブ対応でJQuery不要の高性能スライダーのライブラリです。jQueryに依存しないので読み込まれているjQueryのバージョンの競合を気にする必要が無い上に、他のライブラリに比べて機能が豊富なのが、このSwiper.jsなのです。

 

そもそもセクションとは

Shopifyの各ページの作成はテーマエディタ上部中央でテンプレートを切り替えて作成します。
そしてそのページのテンプレートにセクションを詰め込んでページ全体のデザインをしていきます。

テンプレート切り替え

下記画像はテーマエディター左側の画像です。

セクションの説明

赤枠がセクションになります。そしてそのそれぞれのセクション内の小さな部品がブロックになります。

動的セクションの情報はOS2.0のJSONテンプレートファイルに反映保存されます。静的セクションの場合はテンプレートファイルに手動で登録するかLiquidのsectionタグで出力します。

 

セクションを作る大まかな手順

以下は私のやり方で恐縮ですが、新しくセクションを作る時の手順です。

 

  1. まずは{% schema %}{% endschema %}タグの中身のJSONを大まかに書く。
  2. そのschemaタグに従ってHTMLとLiquidをおおまかにコーディングして、テストする。
  3. CSS調整をする。
  4. 機能を増やしたり、細かいところを作り込んだりしていく。

 

セクションの作成開始

それでは早速、作っていきましょう!

新しいセクションを追加する

コードエディタの「セクション」の下の「新しいセクションを追加する」をクリックしてください。

新しいセクションを追加する

 

新しいセクションの名前を決めます。ここでは「swiper-slideshow.liquid」にしました。

新しいセクションの名前を決める

 

以下の様に新しいセクションが出来ました。

新しいセクション追加完了

ここには{% stylesheet %}タグと{% javascript %}タグがあり、CSSとJavaScriptが記述されるようになっているのですが、実はここではレンダリングされません。
上記タグ内に記述されたCSSやJavaScriptはそれぞれ1つのファイルに結合され、レイアウトのcontent_for_headerオブジェクトでまとめて出力されます。
また、JavaScriptの場合は即時関数でラッピングされます。

しかしながら、上記タグ内ではLiquidが使用できないので使い勝手が悪く、結局はhtmlタグである<script>や<style>の方を使用したり外部ファイル化したりする方が使い勝手がいいので、私の場合は{% stylesheet %}タグと{% javascript %}タグはこの時点で削除しています。

 

schemaタグの作成

この新しいセクションの名前を決めます。nameプロパティを設定します。

 

{% schema %}
  {
    "name": "Swiper Slideshow",
    "settings": []
  }
{% endschema %}

ここでは「Swiper Slideshow」にしました。ここで設定された名前がテーマエディタでの表示名になります。
※英語にしてしまいましたが、日本語でも構いません。

 

スライダーの設定項目を設定

Swiper.jsで設定できるオプションは沢山ありますが、ここでは簡単に「スライド速度」と「次のスライドに切り替わる時間」を設定できるようにしてみます。

デザイナーがテーマエディタでビジュアル設定できる入力設定タイプは沢山ありますが、この場合は「range」が良さそうです。
(リンク先は英語で読むのが辛いと思いますので、また別の機会に日本語で説明します。)

 

{% schema %}
{
  "name": "Swiper Slideshow",
  "settings": [
    {
      "type": "range",
      "id": "speed",
      "min": 100,
      "max": 1000,
      "step": 100,
      "unit": "ミリ秒",
      "label": "スライド速度",
      "default": 500
    },
    {
      "type": "range",
      "id": "delay",
      "min": 100,
      "max": 9000,
      "step": 100,
      "unit": "ミリ秒",
      "label": "スライド切り替わり時間",
      "default": 3500
    }
  ]
}
{% endschema %}

 

以下がsettingsに設定する共通プロパティです。

属性 説明 必須かどうか
type テキストボックスや数値入力ボックスやラジオボタン等、入力タイプを指定します。 必須
id 設定値にアクセスするために使用するIDです。半角英数字でセクション内でユニークな名前をつけてください。 必須
label テーマエディタ上で何を入力するかを表示するラベルを入力します。日本語が使用できるので、簡潔で分かりやすいタイトルを入れてください。 必須
default 設定のデフォルト値を設定します。 任意
info 設定に関する説明文を入力します。 任意

type「range」の場合は上に加えて以下のプロパティがあります。

属性 説明 必須かどうか
min 最小値 必須
max 最大値 必須
step スライダーを動かした時の増減分 必須
unit 必要があればエディタ上での表示用の単位を指定できます。例えば、フォントサイズスライダーを作りたい時はpxを指定します。 任意

ここで注意ですが、idプロパティはセクション内でユニークな名前にしてください。
※別のセクションで使用しているidは使用できます。

 

スライダーの画像を設定できるようにする

スライダーの画像を設定できるように、今度はセクション内の小部品「ブロック」を作成していきます。

まずはブロックの雛形です。ブロックの種類は1種類とは限らないので配列で指定するようになっています。

 

"blocks": [
  {
    "type": "image",
    "name": "スライド画像",
    "settings": []
  }
]

ここで気をつけるのはblocks直下の「type」プロパティです。先程のsettingsでのtypeは入力設定タイプでしたが、ここではidの意味合いが強く、blocks内でユニークな値を設定してください。
ここでの設定値はこの後のpresets(静的セクションの場合はDefault)で使用します。
※なぜidではなくtypeなのかは不明ですが、ブロック設定をid指定する時に混乱しないようにする配慮かもしれません。

ここでは画像とその画像をクリックした時のリンク先を設定できるようにします。

 

"blocks": [
  {
    "type": "image",
    "name": "スライド画像",
    "settings": [
      {
        "type": "image_picker",
        "id": "image",
        "label": "image"
      },
      {
        "type": "url",
        "id": "link",
        "label": "リンク先"
      }
    ]
  }
]

blocks→settingsのtypeは、入力設定タイプにします。ここでは「image_picker(イメージピッカー)」と「url(外部リンクや内部相対パスを設定できるURLピッカーフィールド)」を設定しています。

 

テーマエディターでセクションを動的に追加できるようにする

テーマエディタでセクションを追加したときの初期値等を設定します。
セクションにpresetsプロパティがあればテーマエディタは動的セクションとみなすので、動的セクションを作るときには必須属性になります。
※静的セクションを作る時はdefaultプロパティの方を使用してpresetsプロパティは使用しないでください。

まずはpresetsプロパティの雛形です。

"presets": [
  {
    "name": "Swiper Slideshow",
    "blocks": []
  }
]

 

blocksの無いセクションの場合はnameプロパティだけで十分ですが、blocksで画像を設定するので設定していきます。

ここでnameプロパティは、新しく動的にセクションを追加する時の名前になります。以下の画像の赤枠部分です。

セクション追加時のイメージ

かつてはcategoryプロパティがあってカテゴリーによって自動的にグルーピングしてくれる機能があったのですが、この機能はいつの間にか無くなったようです。
※左のアイコンは、おそらくnameの設定名で自動判定されて出力されているような感じですね。

ここで設定するnameプロパティは、設置後に表示される名前と違ってしまって混乱を招かないよう、特に理由がない限り一番最初に設定したnameプロパティと同じ名前を設定するようにしてください。

そして、テーマエディタで最初にセクションを追加した時に自動的に追加するブロックを指定します。

 

"presets": [
  {
    "name": "Swiper Slideshow",
    "blocks": [
      {
        "type": "image"
      },
      {
        "type": "image"
      }
    ]
  }
]

 

上記例では、typeプロパティがimageのブロックを2つ、自動配置させる例です。


これでschemaの大まかな設定は、とりあえず終了です。ここまでのJOSNを全てつなげたschemaタグは以下のとおりです。

{% schema %}
{
  "name": "Swiper Slideshow",
  "settings": [
    {
      "type": "range",
      "id": "speed",
      "min": 100,
      "max": 1000,
      "step": 100,
      "unit": "ミリ秒",
      "label": "スライド速度",
      "default": 500
    },
    {
      "type": "range",
      "id": "delay",
      "min": 100,
      "max": 9000,
      "step": 100,
      "unit": "ミリ秒",
      "label": "スライド切り替わり時間",
      "default": 3500
    }
  ],
  "blocks": [
    {
      "type": "image",
      "name": "スライド画像",
      "settings": [
        {
          "type": "image_picker",
          "id": "image",
          "label": "image"
        },
        {
          "type": "url",
          "id": "link",
          "label": "リンク先"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Swiper Slideshow",
      "blocks": [
        {
          "type": "image"
        },
        {
          "type": "image"
        }
      ]
    }
  ]
}
{% endschema %}

 

 

表示用のコードをコーディングする(HTML・CSS・Liquid・JavaScript)

ここからはサイトでの表示部分であるメインコンテンツを作成していきます。

メインコンテンツの作成

先程作成したschemaを見ながら、HTMLとLiquidでサクッとコーディングしたのが以下のものです。

 

<section data-section-id="{{ section.id }}" data-section-type="slideshow">
  <div class="swiper">
    <div class="swiper-wrapper">
      {% for block in section.blocks %}
      <div class="swiper-slide">
        <div class="item">
          {%- if block.settings.link != blank -%}<a href="{{ block.settings.link }}">{%- endif -%}
          <img src="{{ block.settings.image | img_url: "1000x" }}" width="{{ block.settings.image.width }}" height="{{ block.settings.image.height }}" alt="{{ block.settings.image.alt | escape }}" />
          {%- if block.settings.link != blank -%}</a>{%- endif -%}
        </div>
      </div>
      {% endfor %}
    </div>
    <div class="swiper-pagination"></div>
    <div class="swiper-button-prev"> </div>
    <div class="swiper-button-next"> </div>
  </div>
</section>

 

ポイント!

<div class=”swiper-button-prev”> </div>
<div class=”swiper-button-next”> </div>
についてです。
Swiper.jsでは「前に戻る」「先に進む」の矢印「<>」を疑似要素afterで表示しています。しかし疑似要素はDOM要素ではないためか、<div>タグの中身(content)が空の場合は環境によって疑似要素が表示されない不具合が出ることがあります。そのため、divでもspanでも何でも良いのですがここでは半角スペースを入れています。

Liquidを簡単に説明します。

「{% for block in section.blocks %}」は、配列section.blocksをindex0から順番に取り出して変数blockに代入しながらループを繰り返します。

{{ block.settings.image | img_url: “1000x” }}」のblock.settings.imageは、shcemaで指定した以下の画像のことで、blocks→settings→idの設定値であるimegeを指定しています。
これでテーマエディタのイメージピッカーで設定した画像のimageオブジェクトが取得できます。

 

"blocks": [
  {
    "type": "image",
    "name": "スライド画像",
    "settings": [
      {
        "type": "image_picker",
        "id": "image",
        "label": "image"
      },
      {
        "type": "url",
        "id": "link",
        "label": "リンク先"
      }
    ]
  }
]

 

「 | img_url: “1000x”」はLiquidのフィルターで、画像のURLを幅1,000px(高さは自動)で返します。
ここではスライダーバナー画像のサイズの幅として1,000pxを想定しています。

 

Swiper.jpのテスト動作用のJavaScriptを作成する

Swiper.js動作用のコードを作成します。Swiperライブラリを読み込み、オプションを設定してSwiperのインスタンスを作るだけでSwiper.jsは動作します。

 

<script type="text/javascript">
  !function(){
    const option = {
      loop: true,
      loopAdditionalSlides: 1,
      slidesPerView: 'auto',
      centeredSlides: true,
      spaceBetween: 0,
      speed: {{ section.settings.speed }}, //移動速度
      breakpoints: {
        1000: {
          slidesPerView: 1,
          spaceBetween: 0
        }
      },
      autoplay: {
        delay: {{ section.settings.delay }}, //スライドするまでの停止時間
        disableOnInteraction: false
      },
      slidesPerGroup: 1,
      initialSlide: 0,
      pagination: {
        el: '.swiper-pagination', 
        clickable: true,
        type: 'bullets'
      },
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      }
    };

    const css = document.createElement('link');
    css.href = 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css';
    css.rel = 'stylesheet';
    css.type = 'text/css';
    document.head.appendChild(css);
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js';
    script.setAttribute('charset', 'UTF-8');
    document.head.appendChild(script);
    script.onload = function() {
      const swiper = new Swiper('.swiper', option);
    };
  }();
</script>

ここでは、動作テスト用なのでセクション内で完結したくSwiperライブラリをJavaScriptで動的に読み込んでいますが、実装時にはレイアウトファイル等に、CSSファイルの場合はhead内に、JSファイルの場合はbodyの閉じタグ直前くらいに配置するかdefer指定するかしたほうが良いでしょう。

 

実際に動かしながらCSS調整をする

この時点でShopifyのコードエディタでファイルを一旦保存し、今度はテーマエディタで新しくセクション「Swiper Slideshow」を追加し、ブロックにテスト用バナー画像(今回の例では幅1,000pxの画像です)を設定してみましょう。

スライド画像を設定する

 

こうして出来たCSSは以下のとおりです。

 

.swiper {
  width: 1000px;
  overflow: visible;
}
.swiper-slide {
  text-align: center;
}
.swiper-slide img {
  width: 1000px;
  height: auto;
}
@media screen and (max-width: 1000px){
  .swiper {
    width: 100%;
  }
  .swiper-slide img {
    width: 100%;
  }    
}

 

 

ポイント!

CSSの調整の時、私の場合はブラウザのDevToolsを利用しています。しかしJavaScriptが動いているとCSS調整しづらいので、この時はJavaScriptを一旦止めています。
・Chromeの場合はSourcesタブに切り替えて「F8」キー
・FireFoxの場合はデバッガータブに切り替えて「F8」キー
これでJavaScriptが一時停止します。

 

 

とりあえず、スライダーセクション完成

これまでに作ったコードの全体像です。

 

<section data-section-id="{{ section.id }}" data-section-type="slideshow">
  <div class="swiper">
    <div class="swiper-wrapper">
      {% for block in section.blocks %}
      <div class="swiper-slide">
        <div class="item">
          {%- if block.settings.link != blank -%}<a href="{{ block.settings.link }}">{%- endif -%}
          <img src="{{ block.settings.image | img_url: "1000x" }}" width="{{ block.settings.image.width }}" height="{{ block.settings.image.height }}" alt="{{ block.settings.image.alt | escape }}" />
          {%- if block.settings.link != blank -%}</a>{%- endif -%}
        </div>
      </div>
      {% endfor %}
    </div>
    <div class="swiper-pagination"></div>
    <div class="swiper-button-prev"> </div>
    <div class="swiper-button-next"> </div>
  </div>
</section>

<style>
  .swiper {
    width: 1000px;
    overflow: visible;
  }
  .swiper-slide {
    text-align: center;
  }
  .swiper-slide img {
    width: 1000px;
    height: auto;
  }
  @media screen and (max-width: 1000px){
    .swiper {
      width: 100%;
    }
    .swiper-slide img {
      width: 100%;
    }    
  } 
</style>

<script type="text/javascript">
  !function(){
    const option = {
      loop: true,
      loopAdditionalSlides: 1,
      slidesPerView: 'auto',
      centeredSlides: true,
      spaceBetween: 0,
      speed: {{ section.settings.speed }}, //移動速度
      breakpoints: {
        1000: {
          slidesPerView: 1,
          spaceBetween: 0
        }
      },
      autoplay: {
        delay: {{ section.settings.delay }}, //スライドするまでの停止時間
        disableOnInteraction: false
      },
      slidesPerGroup: 1,
      initialSlide: 0,
      pagination: {
        el: '.swiper-pagination',
        clickable: true,
        type: 'bullets'
      },
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      }
    };

    const css = document.createElement('link');
    css.href = 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css';
    css.rel = 'stylesheet';
    css.type = 'text/css';
    document.head.appendChild(css);
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js';
    script.setAttribute('charset', 'UTF-8');
    document.head.appendChild(script);
    script.onload = function() {
      const swiper = new Swiper('.swiper', option);
    };
  }();
</script>

{% schema %}
{
  "name": "Swiper Slideshow",
  "settings": [
    {
      "type": "range",
      "id": "speed",
      "min": 100,
      "max": 1000,
      "step": 100,
      "unit": "ミリ秒",
      "label": "スライド速度",
      "default": 500
    },
    {
      "type": "range",
      "id": "delay",
      "min": 100,
      "max": 9000,
      "step": 100,
      "unit": "ミリ秒",
      "label": "スライド切り替わり時間",
      "default": 3500
    }
  ],
  "blocks": [
    {
      "type": "image",
      "name": "スライド画像",
      "settings": [
        {
          "type": "image_picker",
          "id": "image",
          "label": "image"
        },
        {
          "type": "url",
          "id": "link",
          "label": "リンク先"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Swiper Slideshow",
      "blocks": [
        {
          "type": "image"
        },
        {
          "type": "image"
        }
      ]
    }
  ]
}
{% endschema %}

 

スライダーのサンプル

こうして出来たスライダーセクションは、まだまだ改良の余地があります。また、この時点で予想できる不具合は、1ページ内に複数のスライダーを設置した時に動作不良が予想されることです。

もし、株式会社ティーエッチエスのECサイト構築サービスにてご用命いただけるのなら強固なカスタマイズをお約束致しますので、弊社のサービスをぜひともご検討ください。