Shopify + React.js(CDN)でカウントダウン機能を作成する

2024/02/18に公開

はじめに

タイトルに記載してある通り、Shopifyでカウントダウン機能を作成しました。
第四四半期では期間限定のクローズドサイトを公開することになり、商品ページにカウントダウン機能を導入してほしいと要望をいただいており、また、GUI化してお客様が時間設定できるようにしたいとのことなので短期間でどのように実装すればいいのかを考えました。

実装方法

まず、product.jsonのtypeの値と同一するファイル名を開きます。
例:"type": "main-product", だった場合は「main-product.liquid」であり、
そのファイルを開きスキーマを設定します。

{% schema %}
{
  "name": "t:sections.main-product.name",
  "tag": "section",
  "class": "section",
  "blocks": [
    {
      "type": "@app"
    },
    {
      "type": "countdown",
      "name": "カウントダウン",
      "settings": [
        {
          "type": "checkbox",
          "id": "id_countdown",
          "default": false,
          "label": "カウントダウン表示有無",
          "info": "セール終了となる日時を設定ください。8月32日や27時90分等、無効な日時を記載した場合は、カウントダウンタイマーは表示されません。"
        },        
        {
          "type": "number",
          "id": "id_countdown_year",
          "default": 2000,
          "label": "年"
        },
        {
          "type": "number",
          "id": "id_countdown_month",
          "default": 1,
          "label": "月",
          "info": "1-12までの間で設定ください。"
        },
        {
          "type": "number",
          "id": "id_countdown_day",
          "default": 1,
          "label": "日",
          "info": "1-31までの間で設定ください。"
        },
        {
          "type": "number",
          "id": "id_countdown_hours",
          "default": 1,
          "label": "時間",
          "info": "0-23までの間で設定ください。"
        },
        {
          "type": "number",
          "id": "id_countdown_minute",
          "default": 1,
          "label": "分",
          "info": "0-59までの間で設定ください。"          
        },
        {
          "type": "number",
          "id": "id_countdown_second",
          "default": 1,
          "label": "秒",
          "info": "0-59までの間で設定ください。"
        }
      ]
    }
  ]
}
{% endschema %}

スキーマ設定後はGUI画面では以下のように反映されました。
また、一目でわかりやすいように注釈などを加えて実装しました。

次にカウントダウンの空のファイル(snippets/product-countdown.liquid)を作成して該当箇所に埋設します。

{% comment %} カウントダウン {% endcomment %}
{% render 'product-countdown', section: section %}  

snippets/product-countdown.liquidのファイルを開いてカウントダウンの実装します。
今回はReact.jsのCDNを導入しました。

{%- for block in section.blocks -%}
    {%- case block.type -%}
      {%- when 'countdown' -%}
        {%- if block.settings.id_countdown == true-%}
          {% assign countdown_year = block.settings.id_countdown_year | default: 2000 %}
          {% assign month_date = block.settings.id_countdown_month | default: 1 %}
          {% assign countdown_month = month_date | minus: 1 %}          
          {% assign countdown_day = block.settings.id_countdown_day | default: 1 %}
          {% assign countdown_hour = block.settings.id_countdown_hours | default: 1 %}
          {% assign countdown_min = block.settings.id_countdown_minute | default: 1 %}
          {% assign countdown_sec = block.settings.id_countdown_second | default: 1 %}

          <script src="https://unpkg.com/react@18.2.0/umd/react.development.js" crossorigin></script>
          <script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js" crossorigin></script>
          <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> 
          <script type="text/babel">
              const { Component, Fragment } = React;
              const { createRoot } = ReactDOM;

              class CountDown extends Component {
                state = {
                  remaining: {
                    days: 0, hours: 0, minutes: 0, seconds: 0
                  },
                  isExpired: false
                };
                timer;
                distance;

                componentDidMount() {
                  this.setDate();
                  this.counter();
                }

                setDate = () => {
                  const now = new Date().getTime(),
                  countDownDate = new Date({{countdown_year}}, {{countdown_month}}, {{countdown_day}}, {{countdown_hour}}, {{countdown_min}}, {{countdown_sec}}).getTime();

                  // 時間経過した場合
                  if (Math.floor((countDownDate - now) / 1000) < 0) {
                    clearInterval(this.timer);
                    this.setState({ isExpired: true });
                  } else {
                    // カウントダウンの日数が1日未満の場合
                    if( Math.floor((countDownDate - now) / 1000) < 86400 ) {
                      var remaining = {
                        時間: Math.floor((countDownDate - now) / 1000 / 3600) % 24,
                        分:   Math.floor((countDownDate - now) / 1000 / 60) % 60,
                        秒:   Math.floor((countDownDate - now) / 1000) % 60 
                      }
                    // カウントダウンの日数が1日以上の場合
                    } else {
                      var remaining = {
                        日: Math.floor((countDownDate - now) / 1000 / 3600 / 24),
                        時間: Math.floor((countDownDate - now) / 1000 / 3600) % 24,
                        分:   Math.floor((countDownDate - now) / 1000 / 60) % 60,
                        秒:   Math.floor((countDownDate - now) / 1000) % 60 
                      }
                    }
                    this.setState({
                      countDownDate: countDownDate,
                      remaining: remaining,
                      isExpired: false
                    });
                  }
                };

                counter = () => {
                  this.timer = setInterval(() => {
                    this.setDate();
                  }, 1000);
                };

                render() {
                  const { countDownDate, remaining, isExpired } = this.state;

                  return (
                    <Fragment>
                      {!isExpired && !isNaN(countDownDate) ? (
                        <div className="productDetail__countdown__counter">
                          終了まであと  
                          {Object.entries(remaining).map((el, i) => { 

                              return (
                                <div key={i} className="productDetail__countdown__entry">
                                  <div key={el[1]} className="productDetail__countdown__entry-value">
                                    <span className="productDetail__countdown__count">{el[1]}</span>
                                  </div>
                                  <div className="productDetail__countdown__entry-title">{el[0].toUpperCase()}</div>
                                </div>
                              )
                            }
                          )}                          
                        </div>
                      ) : (
                        <p></p>
                      )}
                    </Fragment> 
                  );
                }
              }
              const app = <CountDown />;
              document.querySelectorAll('.productDetail__countdown')
                .forEach( domcontainer => {
                  const root = createRoot(domcontainer);
                  root.render( app );              
                });

          </script>
          <div class="productDetail__countdown"></div>
        {%- endif -%}
    {%- endcase -%}      
  {% endfor %}

後はCSSでレイアウト調整したら完成です。

最後に

今回は、Shopifyでカウントダウン機能を作成した内容を紹介しました。
弊社ではReact.jsを触ることがほとんどなかったのでそのCDNを利用して実装できて良かったです。
これからもShopifyで新規サイト構築が増えそうなので、難易度の高めのタスクがあったらZennで記事を投稿しようかと思います。

Discussion