ShopifyでFlickityを設置しても全然動作しなくて時間を無駄にした。いつも原因がまちまちだがこれまで何度も同じようなことをやらかしてしまっているミスのように思ったので、その備忘録。
Flickity公式
https://flickity.metafizzy.co/#cdn
今回の最終形↓
See the Pen Flickityでスライド後に画像がゆっくりズームアウト by Naoto Yoshikawa (@nanaironanaoto) on CodePen.
CDNであれば
<link rel="stylesheet" href="https://unpkg.com/flickity@2/dist/flickity.min.css">
<script src="https://unpkg.com/flickity@2/dist/flickity.pkgd.min.js"></script>CDNでなければ、Shopifyのassetフォルダに格納しているときの呼び出し例。
Flickityはスクリプトをdefer=”defer”として遅延ロードさせたりすると、スライダーの表示がカクつく。
{{ 'flickity.min.css' | asset_url | stylesheet_tag }}
<script src="{{ 'flickity.pkgd.min.js' | asset_url }}" defer="async"></script>がんばっても、サブリミナル映像ほどの瞬間カクつくので、もうサブリミナルも嫌なら、スライダーにFlickityが適用されたら”flickity-enabled”というクラスが付与されるので、これを検知するまで別の待機画面やをオーバーレイさせるなどするとよいかもしれない。(今回そこまではしないものとします)
ただ、ローディングの画像で画面全体を覆うのは、SEO的にも悪い。ページのローディングを遅くするのがひとつ、もうひとつが、コンテンツがJavaScriptを完全に読み込むまで表示されない場合、検索エンジンがページの重要なコンテンツをインデックスできない可能性がある。
このPRYTHMWORKSでもローディングアニメーションを入れていたらGoogleの検索ロジックが変更になったのとタイミングが合ったのか、ガッツリ自然検索数が落ちた。
公式に記載のバニラ(無加工の)JavaScript実行コードそのままでは実行されない
公式に記載の“Initialize with vanilla JavaScript”
var elem = document.querySelector('.main-carousel');
var flkty = new Flickity( elem, { /* options */ });↓
動かない
↓
スクリプトの実行タイミングが原因なのか、下のだと実行できた。。
↓
document.addEventListener('DOMContentLoaded', function() {
    var elem = document.querySelector('.main-carousel');
    var flkty = new Flickity(elem, { /* options */ });
});document.addEventListener('DOMContentLoaded', function() { /*実施内容*/ }で公式のJSコードをラップしているだけ。
JavaScriptがDOM要素のロードより先に実行されてしまっていたらしい。
指定した要素がまだ存在しない状態でFlickityが初期化しようとするとエラーが発生する。
わかったのは、Shopifyでtheme.liquidに直書きすると管理が汚くなるので、sectionのliquiudコードに一連のコードを記述していたことが原因だった。
どうやらShopifyではtheme.liquidで参照していてもsectionに記述したコードの実行順序が、theme.liquidのコードで見た上からの読み込みじゃないようだ。
検証していくと、sectionのコードは、テーマの最も浅いレベルのコードが読み込まれたあとに読まれていることがわかった。なので、sectionに格納しても汎用的に使用できる形としては、以下が完成系だ。
{{ 'flickity.min.css' | asset_url | stylesheet_tag }}
<script src="{{ 'flickity.pkgd.min.js' | asset_url }}" defer="async"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
    var elem = document.querySelector('.main-carousel');
    var flkty = new Flickity(elem, { 
      /* Flickityが提供しているオプションをここに記述 */    });
});
</script>
<div class="banner_wrap">
  <div class="main-gallery">
  {% for block in section.blocks %}
    <a class="slider-cell" href="{{ block.settings.url }}">
      {{ block.settings.slider_image | image_url: width: 1600 | image_tag }}
    </a>
  {% endfor %}
  </div>
</div>
{% schema %}
{
  "name": "スライダー画像",
  "blocks": [
      {
        "name": "slider",
        "type": "header",
        "settings": [
          {
            "type" : "image_picker",
            "id" : "slider_image",
            "label" : "画像"
          },
          {
            "type" : "url",
            "id" : "url",
            "label" : "リンク先"
          }
        ]
      }
    ],
  "presets" : [
    {
      "name" : "スライダー画像",
      "category" : "Custom"
    }
  ]
}
{% endschema %}公式からも提供されている、changeイベントを取得してjsで自分の好みのイベントを作成可能。ここでは、画像がスライド後にゆっくりズームアウトするエフェクトを追加した。
初期状態が1.2倍のズームイン状態で、スライド後に3秒かけて1倍サイズにズームアウトする。
最終形がズームインだと拡大された分、解像度が粗くなるのが目立つから、最終形はズームアウトのほうがいいんじゃないかと思う。
See the Pen Flickityでスライド後に画像がゆっくりズームアウト by Naoto Yoshikawa (@nanaironanaoto) on CodePen.
かつ、デスクトップとモバイルでそれぞれの適正サイズ、適正縦横比で、表示するスライダーを出し分けることもしている。記述した仕様ではデスクトップとモバイルそれぞれで分けて登録する必要がある。
{{ 'flickity.min.css' | asset_url | stylesheet_tag }}
<script src="{{ 'flickity.pkgd.min.js' | asset_url }}" defer="async"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
    var elem = document.querySelector('.main-gallery');
    var flkty = new Flickity(elem, { 
      autoPlay: 3000,
      wrapAround: true,
      prevNextButtons: true,
      pageDots: false
    });
    // スライドが変更されるたびに発生するイベント
    flkty.on('change', function(index) {
      // すべてのimg要素から特定のクラスを削除
      var imgs = elem.querySelectorAll('img');
      imgs.forEach(function(img) {
        img.classList.remove('active-slide-image');
      });
    
      // 新たに表示されるスライドのimg要素にクラスを追加
      var selectedImg = flkty.selectedElement.querySelector('img');
      if (selectedImg) {
        selectedImg.classList.add('active-slide-image');
      }
    });
});
  document.addEventListener('DOMContentLoaded', function() {
    var elem = document.querySelector('.main-gallery-mobile');
    var flkty = new Flickity(elem, {
      autoPlay: 3000,
      wrapAround: true,
      prevNextButtons: true,
      pageDots: false
    });
    // スライドが変更されるたびに発生するイベント
    flkty.on('change', function(index) {
      // すべてのimg要素から特定のクラスを削除
      var imgs = elem.querySelectorAll('img');
      imgs.forEach(function(img) {
        img.classList.remove('active-slide-image');
      });
    
      // 新たに表示されるスライドのimg要素にクラスを追加
      var selectedImg = flkty.selectedElement.querySelector('img');
      if (selectedImg) {
        selectedImg.classList.add('active-slide-image');
      }
    });
});
</script>
<style>
.main-gallery img {
  transition: transform 3s ease-out;
  transition-duration: transform 0.7s ease-out;
  transform: scale(1.2);
}
.main-gallery img.active-slide-image {
  transform: scale(1);
}
.flickity-prev-next-button {
  width: 24px ;
  height: 24px ;
}
.slider-cell {
  width: 100%;
  height: 600px;
  background: #fff;
  margin: 0 5px;
  overflow: hidden;
}
.slider-cell img {
  width: 100%;
  height: 100%;
}
@media screen and (max-width: 767px) {
  .pc {
    display: none;
  }
  .slider-cell {
      width: 100%;
      height: 400px;
  }
}
@media screen and (min-width: 768px) {
  .sp {
    display: none;
  }
}
</style>
<!--pc-->
<div class="banner_wrap pc">
  <div class="main-gallery main-gallery-desktop">
  {% for block in section.blocks %}
    <a class="slider-cell" href="{{ block.settings.bnr_url }}">
      {{ block.settings.banner_pc | image_url: width: 2000 | image_tag }}
    </a>
  {% endfor %}
  </div>
</div>
<!--sp-->
<div class="banner_wrap sp">
  <div class="main-gallery main-gallery-mobile">
  {% for block in section.blocks %}
    <a class="slider-cell" href="{{ block.settings.url }}">
      {{ block.settings.banner_sp | image_url: width: 900 | image_tag }}
    </a>
  {% endfor %}
  </div>
</div>
{% schema %}
{
  "name": "スライダー画像",
  "blocks": [
      {
        "name": "image",
        "type": "header",
        "settings": [
          {
            "type" : "image_picker",
            "id" : "banner_pc",
            "label" : "Pick an Image(PC)"
          },
          {
            "type" : "image_picker",
            "id" : "banner_sp",
            "label" : "Pick an Image(SP)"
          },
          {
            "type" : "url",
            "id" : "url",
            "label" : "リンク先"
          }
        ]
      }
    ],
  "presets" : [
    {
      "name" : "スライダー画像",
      "category" : "Custom"
    }
  ]
}
{% endschema %}
最近の海外のテーマとか見てると、こんなことしてなくて、トリミング位置が厳格じゃないような一枚の「写真」を登録して、その写真の上にHTMLテキストのフォントをオーバーレイしていることが多いように思う。
「バナー画像」として規定サイズにトリミングした写真に、PhotoShopで文字装飾をした画像を登録する手法ではないのだ。たしかにそれだと、毎回バナー更新するためにデザイナーの手を借りることになるので、運営者が研ぎ澄まされたセンスでフットワーク軽く更新するスタイルなのは海外のテーマの登録方法だ。なので、その制作現場の人員リソースの関係や、運営者が少数だが抑えるポイントとして、素で「よい写真」を使用していこうというポリシーが共有されていれば、いい方法だと思う。
加工が必要になるのは、ある一枚ではイケてない、力の弱い写真しかない場合、という理由もあると思う。今は撮影センスがあればiPhoneでも力のある写真は撮れる。センス大事。。
それに、「写真の上にHTMLテキストのフォントをオーバーレイ」のメリットとしては、「パララックス」や「フォントのみあとからフェードアウト」や「画像のみゆっくりズームアウトしてくる」など、動きのある表現ができる。
Shopifyで、上のようなセクションにさらに手を加えて、スライダー画像の上に文字がオーバーレイするように設計したセクションを用意すれば、そのセクションに登録する画像とテキストの変更だけで特集バナーの運用もノーコード、ノーデザイナーでいける。この体制ではどっちかというと力のある写真を撮れる者がいることが重要になってくる。
余談でしたが、以上です!