star back image
people4
電飾 電飾
moon
astronaut

【shopify】ジェンダー切り替えswiperスライダー

BLOG shopifyWEBログ
読了約:109分

ショッピングサイトのTOPページにあるメインビジュアルのswiperスライダーで、ボタンで女性/男性のビジュアルを切り替えたいと言う要件がありました。

以下のような実装をしていましたところ。

関連記事:【cookie】ジェンダー切り替えボタン
https://neo.astrowave.jp/blog/21533/
サンプル
https://astrowave.jp/amnesia_record/gender_cookie.php

これに修正依頼がありました。

どういう修正ですか?

「WOMENとMENのイベントの数が一緒とは限らない。」と言うのです。

つまり、WOMENとMENは必ず対になっているわけでなく、WOMENが1つで、MENが3つの時も想定されるので修正したい。という修正です。

この「男女対イベント想定」の仕様では破綻してしまいます。

スライド(1つ)の中にWOMEN/MENの画像を入れ込んで、それをボタンで切替え表示していました。

WOMENだけのイベント登録だと、切替えるとMENはグレー色?

イベントが必ず対になる想定なんて、普通しますかね。。

(;゚□゚)ガーン!!

WOMENとMENのイベントの数が違っても破綻しない、柔軟なジェンダー切り替えスライダーにしたいです。

【共有】実装手順とliquidコード

柔軟なジェンダー切り替えスライダーの実装コードを共有です。

要件は以下です。

  • コピペで実装可能
  • 柔軟なスライド数: WOMENが5枚、MENが3枚でもOK
  • Shopify管理画面: 直感的な設定画面
  • Cookie保存: ユーザーの選択を30日間記憶
  • URLパラメータ対応: ?gender=men で直接アクセス可能
  • 動画対応: 画像だけでなく動画スライドも可能
  • Safari低電力モード対応: 自動検出機能付き

ステップ1: ファイル作成

  • sections/slideshow.liquid を作成

ステップ 2: 管理画面設定

  • テーマカスタマイザー → セクションを追加 → Slideshow
  • ブロックを追加して画像・テキストを設定

ステップ 3: 動作確認

  • https://yourstore.com/?gender=women → WOMEN表示
  • https://yourstore.com/?gender=men → MEN表示

全て1つにまとめたコードが以下のslideshow.liquidです。

sections/slideshow.liquid

<!-- Swiper CDN追加 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />

<style>
/* 既存のスタイル */
/* スライダー切り替え用のスタイル */
.p-top-hero__slider {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.5s ease, visibility 0.5s ease;
}
.p-top-hero__slider.slider-women {
  position: relative;
}
.p-top-hero__slider.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* Safari省電力モード時の動画を隠す */
body.safari-low-power-mode .c-slider-hero__video {
  display: none !important;
}

/* 他のセクション用のgender切り替えスタイル */
.gender-switch-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
}
.gender-content {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.5s ease, visibility 0.5s ease;
}
.gender-content.gender-women {
  position: relative;
}
.gender-content.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.c-slider-hero .swiper-slide::after {
    content: "";
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 15%;
    background: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgb(0, 0, 0)));
    background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.35) 100%);
    opacity: 1;
    z-index: 2;
}
.p-top-hero__slider .c-slider-hero__image img,
.p-top-hero__slider .c-slider-hero__image svg {
  width: 100%;
  height: 100vh;
  object-fit: cover;
  object-position: center top;
}

@media screen and (min-width: 768px) {
  .c-slider-hero__content {
    /* top: 50%;
    transform: translateY(50%); */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
  }
  .c-slider-hero__content a {
    width: 93.75%;
    align-items: center;
  }
  .c-slider-hero__title {
    width: 100%;
    font-family: helvetica-neue-lt-pro, Gothic MB101 DemiBold, sans-serif !important;
    font-weight: 700;
    font-size: 32px !important;
    line-height: 110%;
    letter-spacing: -0.01em;
    text-shadow: 0px 0px 15px rgba(0, 0, 0, 0.05);
  }
  .c-slider-hero__button {
    margin-top: 18px;
  }
  .c-link-parent__button {
    min-width: 120px;
    text-align: center;
    padding-top: 12.5px;
    padding-right: 20px;
    padding-bottom: 11.5px;
    padding-left: 20px;
    border-radius: 100px;
    border: 1px solid #fff;
    background: transparent;
    font-family: helvetica-neue-lt-pro, Gothic MB101 DemiBold, sans-serif;
    font-weight: 600;
    font-size: 12px;
    line-height: 100%;
    letter-spacing: 0.05em;
    color: #fff;
    text-decoration: none;
    text-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2);
    background-color: rgba(255, 255, 255, 0.1);
    transition: background-color .3s ease, color .3s ease;
  }
  @media(any-hover: hover) {
    .c-link-parent__button:hover {
      background-color: #fff;
    }
    .c-link-parent__button:hover {
      color: #000A82;
    }
  }
  .gender {
    position: absolute;
    bottom: 40px;
    right: 40px;
    z-index: 1;
    display: flex;
    width: 286px;
    border: 0px solid #fff;
  }
  .gender li {
    width: 50%;
  }
  .gender li a {
    display: block;
    width: 100%;
    padding: 14px 0;
    font-weight: 700;
    font-size: 12px;
    line-height: 1;
    letter-spacing: 0.01em;
    text-align: center;
    background: #ffffff;
    transition: all 0.3s ease;
  }
  .gender li a span {
    opacity: 100%;
    color: #adadad;
  }
  .gender li a.active{
    font-family: helvetica-neue-lt-pro;
    font-weight: 700;
    font-size: 12px;
    line-height: 1;
    letter-spacing: 0.01em;
    background: #000A82;
  }
  .gender li a.active span {
    opacity: 100%;
    color: #FFFFFF;
  }

  @media(any-hover: hover) {
    .gender li a:hover {
      background-color: #000A82;
    }
    .gender li a:hover span {
      color: #FFFFFF;
    }
    .gender li a:hover.active {
      background-color: #000A82;
    }
    .gender li a:hover.active span {
      color: #FFFFFF;
    }
  }

  /* ページネーション */
  .special-swiper-container .swiper-pagination {
    width: auto !important;
    left: 15px !important;
    bottom: 20px !important;
    text-align: left;
  }
  .special-swiper-container .swiper-pagination-bullet{
    position: relative;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 1px solid #FFF;
    opacity: 1;
    margin:0 var(--swiper-pagination-bullet-horizontal-gap,4.5px) !important;
  }
  .c-slider-hero__pagination {
    position: absolute;
    left: 0;
    bottom: 38px;
    right: 0;
    text-align: left;
    margin: 0 40px;
    z-index: 2;
  }
  .c-slider-hero__pagination .swiper-pagination-bullet {
    width: 8px;
    height: 8px;
    margin: 0 3px !important;
  }
  .special-swiper-container .swiper-pagination-bullet:not(.swiper-pagination-bullet-active) {
    background: transparent !important;
    border: 0px solid #FFF;
  }
  .special-swiper-container .swiper-pagination-bullet::before {
    content: "";
    position: absolute;
    width:10px;
    height: 10px;
    background:#FFF !important;
    top: 0;
    left: 0;
    border: 0px solid #FFF;
    border-radius: 50%;
    transition: transform .3s;
    transform: scale(0.3)
  }
  .special-swiper-container .swiper-pagination-bullet.swiper-pagination-bullet-active::before{
    opacity: 0;
    transform: scale(1);
    trasition: none;
  }
}
@media screen and (max-width: 767px) {
  .c-slider-hero__content {
    /* top: 50%;
    transform: translateY(50%); */
    height: unset;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
  }
  .c-slider-hero__content a {
    width: 93.75%;
    height: unset;
    min-height: 100px;
    align-items: center;
  }
  .c-slider-hero__title {
    width: 100%;
    font-family: helvetica-neue-lt-pro, Gothic MB101 DemiBold, sans-serif !important;
    font-weight: 700;
    font-size: 24px;
    line-height: 110%;
    letter-spacing: -0.02em;
    text-align: center;
    text-shadow: 0px 0px 15px rgba(0, 0, 0, 0.05);
  }
  .c-slider-hero__button {
    margin-top: 18px;
  }
  .c-link-parent__button {
    min-width: 90px;
    text-align: center;
    padding-top: 9.5px;
    padding-right: 15px;
    padding-bottom: 9.5px;
    padding-left: 15px;
    border-radius: 100px;
    border: 1px solid #fff;
    background: transparent;
    font-family: helvetica-neue-lt-pro, Gothic MB101 DemiBold, sans-serif;
    font-weight: 600;
    font-size: 11px;
    line-height: 100%;
    letter-spacing: 0.05em;
    color: #fff;
    text-decoration: none;
    text-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25);
    background-color: rgba(255, 255, 255, 0.1);
    transition: background-color .3s ease, color .3s ease;
  }

  .c-slider-hero__pagination .swiper-pagination-bullet {
    width: 8px;
    height: 8px;
    margin: 0 3px !important;
  }
  .c-slider-hero__pagination {
    position: absolute;
    left: 0;
    bottom: 58px;
    right: 0;
    text-align: center;
    z-index: 2;
  }
  .gender {
    position: absolute;
    bottom: 0;
    right: 0;
    z-index: 1;
    display: flex;
    width: 100%;
    border-top: 0px solid #fff;
    border-bottom: 0px solid #fff;
  }
  .gender li {
    width: 50%;
  }
  .gender li a {
    display: block;
    width: 100%;
    padding: 14px 0;
    font-weight: 700;
    font-size: 12px;
    line-height: 1;
    letter-spacing: 0.01em;
    text-align: center;
    background: #ffffff;
    transition: all 0.3s ease;
  }
  .gender li a span {
    opacity: 100%;
    color: #adadad;
  }
  .gender li a.active{
    font-family: helvetica-neue-lt-pro;
    font-weight: 700;
    font-size: 12px;
    line-height: 1;
    letter-spacing: 0.01em;
    background: #000A82;
  }
  .gender li a.active span {
    opacity: 100%;
    color: #FFFFFF;
  }
}
</style>

<div class="l-section p-top-hero js-section-top-hero js-slider-hero2">
  {% if template == 'index' %}
  <h1 class="p-top-hero__logo">
    <img src="{{ 'logo_white.svg' | asset_url }}" width="331" height="35" alt="Your Brand Name">
  </h1>
  {% endif %}
  <!-- Women用スライダー -->
  <div class="p-top-hero__slider slider-women swiper c-slider-hero js-slider-women is-active">
    <div class="swiper-wrapper">
      {%- for block in section.blocks -%}
      {%- if block.settings.image != blank -%}
      <div class="swiper-slide">
        <div class="c-slider-hero__image">
          {%- liquid
            assign sizes = '100vw'
            assign widths = '375, 550, 750, 1100, 1500, 1780, 2000, 3000, 3840'
            assign fetch_priority = 'auto'
            if section.index == 1
              assign fetch_priority = 'high'
            endif
          -%}
          {%- assign htmlalt = block.settings.heading | strip_html -%}
          {%- if forloop.first %}
            <picture>
              <source
              media="(max-width: 767px)"
              srcset="
                {{ block.settings.image_sp | image_url: width: 375 }} 375w,
                {{ block.settings.image_sp | image_url: width: 550 }} 550w,
                {{ block.settings.image_sp | image_url: width: 750 }} 750w,
                {{ block.settings.image_sp | image_url: width: 1100 }} 1100w,
                {{ block.settings.image_sp | image_url: width: 1500 }} 1500w,
                {{ block.settings.image_sp | image_url: width: 1780 }} 1780w,
                {{ block.settings.image_sp | image_url: width: 2000 }} 2000w,
                {{ block.settings.image_sp | image_url: width: 3000 }} 3000w,
                {{ block.settings.image_sp | image_url: width: 3840 }} 3840w,
              ">
              {{
                block.settings.image
                | image_url: width: 3840
                | image_tag: sizes: sizes, widths: widths, fetchpriority: fetch_priority,alt:htmlalt
              }}
            </picture>
          {%- else -%}
            <picture>
              <source
              media="(max-width: 767px)"
              srcset="
                {{ block.settings.image_sp | image_url: width: 375 }} 375w,
                {{ block.settings.image_sp | image_url: width: 550 }} 550w,
                {{ block.settings.image_sp | image_url: width: 750 }} 750w,
                {{ block.settings.image_sp | image_url: width: 1100 }} 1100w,
                {{ block.settings.image_sp | image_url: width: 1500 }} 1500w,
                {{ block.settings.image_sp | image_url: width: 1780 }} 1780w,
                {{ block.settings.image_sp | image_url: width: 2000 }} 2000w,
                {{ block.settings.image_sp | image_url: width: 3000 }} 3000w,
                {{ block.settings.image_sp | image_url: width: 3840 }} 3840w,
              ">
              {{
                block.settings.image
                | image_url: width: 3840
                | image_tag: loading: 'lazy', sizes: sizes, widths: widths,alt:htmlalt
              }}
            </picture>
          {%- endif -%}

          {% comment %} Women用動画URLが設定されている場合 {% endcomment %}
          {%- if block.settings.video -%}
            {%- if block.settings.video_sp -%}
              <video class="js-video-common c-slider-hero__video c-slider-hero__video--responsive" 
                     data-poster-pc="{{ block.settings.image | image_url: width: 3840 }}" 
                     poster="{{ block.settings.image_sp | image_url: width: 3840 }}" 
                     muted playsinline autoplay loop control="false">
                <source media="(max-width: 767px)" src="{{ block.settings.video_sp.sources[1].url }}" type="video/mp4">
                <source media="(min-width: 768px)" src="{{ block.settings.video.sources[1].url }}" type="video/mp4">
              </video>
            {%- else -%}
              <video class="js-video-common c-slider-hero__video c-slider-hero__video--pc" 
                     poster="{{ block.settings.image | image_url: width: 3840 }}" 
                     muted playsinline autoplay loop control="false" src="{{ block.settings.video.sources[1].url }}"></video>
            {%- endif -%}
            
          {%- else -%}
            {%- if block.settings.video_sp -%}
              <video class="js-video-common c-slider-hero__video c-slider-hero__video--sp" 
                     poster="{{ block.settings.image_sp | image_url: width: 3840 }}" 
                     muted playsinline autoplay loop control="false" src="{{ block.settings.video_sp.sources[1].url }}"></video>
            {%- endif -%}
          {%- endif -%}
        </div>

        <div class="c-slider-hero__content">
          {% if block.settings.link %}
          <a class="c-link-parent" href="{{ block.settings.link }}">
          {%- endif -%}
          {%- if block.settings.heading != blank -%}
            <h2 class="c-slider-hero__title u-font-en-h1 u-font-en-bold u-font-ja-bold">{{ block.settings.heading }}</h2>
          {%- endif -%}
          {%- if block.settings.button_label != blank -%}
            <div class="c-slider-hero__button">
              <span class="c-link-parent__button">
              {{ block.settings.button_label }}
              </span>
            </div>
          {%- endif -%}
          {% if block.settings.link %}
            </a>
          {%- endif -%}
        </div>
      </div>
      {%- endif -%}
      {%- endfor -%}
    </div>
    <div class="c-slider-hero__pagination c-slider-hero__pagination--white js-slider-pagination-women"></div>
  </div>

  <!-- Men用スライダー -->
  <div class="p-top-hero__slider slider-men swiper c-slider-hero js-slider-men">
    <div class="swiper-wrapper">
      {%- for block in section.blocks -%}
      {%- if block.settings.image_men != blank -%}
      <div class="swiper-slide">
        <div class="c-slider-hero__image">
          {%- liquid
            assign sizes = '100vw'
            assign widths = '375, 550, 750, 1100, 1500, 1780, 2000, 3000, 3840'
            assign fetch_priority = 'auto'
            if section.index == 1
              assign fetch_priority = 'high'
            endif
          -%}
          {%- assign htmlalt = block.settings.heading_men | strip_html -%}
          {%- if forloop.first %}
            <picture>
              <source
              media="(max-width: 767px)"
              srcset="
                {{ block.settings.image_sp_men | image_url: width: 375 }} 375w,
                {{ block.settings.image_sp_men | image_url: width: 550 }} 550w,
                {{ block.settings.image_sp_men | image_url: width: 750 }} 750w,
                {{ block.settings.image_sp_men | image_url: width: 1100 }} 1100w,
                {{ block.settings.image_sp_men | image_url: width: 1500 }} 1500w,
                {{ block.settings.image_sp_men | image_url: width: 1780 }} 1780w,
                {{ block.settings.image_sp_men | image_url: width: 2000 }} 2000w,
                {{ block.settings.image_sp_men | image_url: width: 3000 }} 3000w,
                {{ block.settings.image_sp_men | image_url: width: 3840 }} 3840w,
              ">
              {{
                block.settings.image_men
                | image_url: width: 3840
                | image_tag: sizes: sizes, widths: widths, fetchpriority: fetch_priority,alt:htmlalt
              }}
            </picture>
          {%- else -%}
            <picture>
              <source
              media="(max-width: 767px)"
              srcset="
                {{ block.settings.image_sp_men | image_url: width: 375 }} 375w,
                {{ block.settings.image_sp_men | image_url: width: 550 }} 550w,
                {{ block.settings.image_sp_men | image_url: width: 750 }} 750w,
                {{ block.settings.image_sp_men | image_url: width: 1100 }} 1100w,
                {{ block.settings.image_sp_men | image_url: width: 1500 }} 1500w,
                {{ block.settings.image_sp_men | image_url: width: 1780 }} 1780w,
                {{ block.settings.image_sp_men | image_url: width: 2000 }} 2000w,
                {{ block.settings.image_sp_men | image_url: width: 3000 }} 3000w,
                {{ block.settings.image_sp_men | image_url: width: 3840 }} 3840w,
              ">
              {{
                block.settings.image_men
                | image_url: width: 3840
                | image_tag: loading: 'lazy', sizes: sizes, widths: widths,alt:htmlalt
              }}
            </picture>
          {%- endif -%}

          {% comment %} Men用動画URLが設定されている場合 {% endcomment %}
          {%- if block.settings.video_men -%}
            {%- if block.settings.video_sp_men -%}
              <video class="js-video-common c-slider-hero__video c-slider-hero__video--responsive" 
                     data-poster-pc="{{ block.settings.image_men | image_url: width: 3840 }}" 
                     poster="{{ block.settings.image_sp_men | image_url: width: 3840 }}" 
                     muted playsinline autoplay loop control="false">
                <source media="(max-width: 767px)" src="{{ block.settings.video_sp_men.sources[1].url }}" type="video/mp4">
                <source media="(min-width: 768px)" src="{{ block.settings.video_men.sources[1].url }}" type="video/mp4">
              </video>
            {%- else -%}
              <video class="js-video-common c-slider-hero__video c-slider-hero__video--pc" 
                     poster="{{ block.settings.image_men | image_url: width: 3840 }}" 
                     muted playsinline autoplay loop control="false" src="{{ block.settings.video_men.sources[1].url }}"></video>
            {%- endif -%}
          {%- else -%}
            {%- if block.settings.video_sp_men -%}
              <video class="js-video-common c-slider-hero__video c-slider-hero__video--sp" 
                     poster="{{ block.settings.image_sp_men | image_url: width: 3840 }}" 
                     muted playsinline autoplay loop control="false" src="{{ block.settings.video_sp_men.sources[1].url }}"></video>
            {%- endif -%}
          {%- endif -%}
        </div>

        <div class="c-slider-hero__content">
          {% if block.settings.link_men %}
          <a class="c-link-parent" href="{{ block.settings.link_men }}">
          {%- endif -%}
          {%- if block.settings.heading_men != blank -%}
            <h2 class="c-slider-hero__title u-font-en-h1 u-font-en-bold u-font-ja-bold">{{ block.settings.heading_men }}</h2>
          {%- endif -%}
          {%- if block.settings.button_label_men != blank -%}
            <div class="c-slider-hero__button">
              <span class="c-link-parent__button">
              {{ block.settings.button_label_men }}
              </span>
            </div>
          {%- endif -%}
          {% if block.settings.link_men %}
            </a>
          {%- endif -%}
        </div>
      </div>
      {%- endif -%}
      {%- endfor -%}
    </div>
    <div class="c-slider-hero__pagination c-slider-hero__pagination--white js-slider-pagination-men"></div>
  </div>
  <ul class="gender">
    <li><a href="#" class="gender-btn active" data-gender="women"><span>WOMEN</span></a></li>
    <li><a href="#" class="gender-btn" data-gender="men"><span>MEN</span></a></li>
  </ul>
</div>
{%- if request.design_mode -%}
  <script src="{{ 'theme-editor.js' | asset_url }}" defer="defer"></script>
{%- endif -%}


<!-- Swiper JS CDN -->
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>

<script>
// ページネーションアニメーション用の変数
var paginationAnimationId;

// ページネーションのスムーズアニメーション関数
function animatePaginationSmooth(activeBullet, duration) {
  duration = duration || 7000;
  if (!activeBullet) return;
  
  var start = null;
  if (paginationAnimationId) {
    cancelAnimationFrame(paginationAnimationId);
  }
  
  function step(timestamp) {
    if (!start) start = timestamp;
    var elapsed = timestamp - start;
    var progress = Math.min((elapsed / duration) * 100, 100);
    activeBullet.style.background = 'conic-gradient(#FFF 0%, #FFF ' + progress + '%, transparent ' + progress + '%, transparent 100%)';
    
    if (elapsed < duration) {
      paginationAnimationId = requestAnimationFrame(step);
    } else {
      activeBullet.style.background = 'conic-gradient(#FFF 0%, #FFF 100%, transparent 100%, transparent 100%)';
    }
  }
  paginationAnimationId = requestAnimationFrame(step);
}

// Cookie操作のヘルパー関数
function setCookie(name, value, days) {
  var expires = "";
  if (days) {
    var date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    expires = "; expires=" + date.toUTCString();
  }
  document.cookie = name + "=" + value + expires + "; path=/";
}

function getCookie(name) {
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for(var i = 0; i < ca.length; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') c = c.substring(1, c.length);
    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
  }
  return null;
}

// ジェンダー切り替えの状態を保存する関数
function saveGenderState(gender) {
  setCookie('selected_gender', gender, 30); // 30日間保持
}

// ジェンダー切り替えの状態を復元する関数
function restoreGenderState() {
  var savedGender = getCookie('selected_gender');
  if (savedGender && (savedGender === 'women' || savedGender === 'men')) {
    return savedGender;
  }
  return 'women'; // デフォルトはwomen
}

// スライダーインスタンスを格納する変数
var womenSlider = null;
var menSlider = null;

// 初期化フラグ
var womenInitialized = false;
var menInitialized = false;

// スライダーの設定値
// ★ スライド速度を変更したい場合は、この値のみを変更してください ★
// ページネーションアニメーションも自動的に連動します
var SLIDER_AUTOPLAY_DELAY = 7000; // 単位: ミリ秒 (7000 = 7秒)

// ジェンダー切り替えの表示を更新する関数
function updateGenderDisplay(gender) {
  // ボタンの状態を更新
  var genderBtns = document.querySelectorAll('.gender-btn');
  genderBtns.forEach(function(btn) {
    if (btn.getAttribute('data-gender') === gender) {
      btn.classList.add('active');
    } else {
      btn.classList.remove('active');
    }
  });

  // スライダーの表示を更新
  var womenSliderEl = document.querySelector('.slider-women');
  var menSliderEl = document.querySelector('.slider-men');
  
  if (gender === 'women') {
    if (womenSliderEl) {
      womenSliderEl.classList.add('is-active');
    }
    if (menSliderEl) {
      menSliderEl.classList.remove('is-active');
    }
    // Women用スライダーのautoplayを開始
    if (womenSlider && womenSlider.autoplay) {
      womenSlider.autoplay.start();
    }
    // Men用スライダーのautoplayを停止
    if (menSlider && menSlider.autoplay) {
      menSlider.autoplay.stop();
    }
    // Women用のページネーションアニメーションを開始(初期化済みの場合はスキップ)
    setTimeout(function() {
      if (!womenInitialized) {
        var womenPagination = document.querySelector('.js-slider-pagination-women');
        if (womenPagination) {
          var activeBullet = womenPagination.querySelector('.swiper-pagination-bullet-active');
          if (activeBullet) {
            animatePaginationSmooth(activeBullet, SLIDER_AUTOPLAY_DELAY);
          }
        }
      }
    }, 100);
  } else if (gender === 'men') {
    if (womenSliderEl) {
      womenSliderEl.classList.remove('is-active');
    }
    if (menSliderEl) {
      menSliderEl.classList.add('is-active');
    }
    // Men用スライダーのautoplayを開始
    if (menSlider && menSlider.autoplay) {
      menSlider.autoplay.start();
    }
    // Women用スライダーのautoplayを停止
    if (womenSlider && womenSlider.autoplay) {
      womenSlider.autoplay.stop();
    }
    // Men用のページネーションアニメーションを開始(初期化済みの場合はスキップ)
    setTimeout(function() {
      if (!menInitialized) {
        var menPagination = document.querySelector('.js-slider-pagination-men');
        if (menPagination) {
          var activeBullet = menPagination.querySelector('.swiper-pagination-bullet-active');
          if (activeBullet) {
            animatePaginationSmooth(activeBullet, SLIDER_AUTOPLAY_DELAY);
          }
        }
      }
    }, 100);
  }

  // 他のセクションのgender-switch-wrapper要素も更新
  document.querySelectorAll('.gender-switch-wrapper').forEach(function(wrapper) {
    var all = wrapper.querySelectorAll('.gender-content');
    var target = wrapper.querySelector('.gender-content.gender-' + gender);
    if (target && all.length > 0) {
      fadeSwitch(target, all);
    }
  });
  
  // デバッグ用ログ(開発時のみ)
  if (window.location.hostname === 'localhost' || window.location.hostname.includes('dev')) {
    console.log('Gender updated to:', gender, 'Found wrappers:', document.querySelectorAll('.gender-switch-wrapper').length);
  }
}

// ジェンダー切り替え
function fadeSwitch(target, all) {
  if (!target || !all || all.length === 0) return;
  
  all.forEach(function(el) {
    if (el === target) {
      el.classList.add('is-active');
      el.style.opacity = '1';
      el.style.visibility = 'visible';
      el.style.pointerEvents = 'auto';
    } else {
      el.classList.remove('is-active');
      el.style.opacity = '0';
      el.style.visibility = 'hidden';
      el.style.pointerEvents = 'none';
    }
  });
}

// スライダーを初期化する関数
function initializeSliders() {
  // Women用スライダーを初期化
  var womenSliderEl = document.querySelector('.js-slider-women');
  var womenPaginationEl = document.querySelector('.js-slider-pagination-women');
  if (womenSliderEl && typeof Swiper !== 'undefined') {
    var womenSlides = womenSliderEl.querySelectorAll('.swiper-slide');
    if (womenSlides.length > 0) {
      womenSlider = new Swiper(womenSliderEl, {
        speed: 900,
        slidesPerView: 1,
        loop: womenSlides.length > 1,
        threshold: 10,
        autoplay: womenSlides.length > 1 ? {
          delay: SLIDER_AUTOPLAY_DELAY,
          disableOnInteraction: false,
          pauseOnMouseEnter: false
        } : false,
        pagination: {
          el: womenPaginationEl,
          clickable: true
        },
        on: {
          init: function() {
            // 初期化時にアクティブなスライダーのみアニメーション開始(短めのアニメーション)
            setTimeout(function() {
              var womenSliderContainer = document.querySelector('.slider-women');
              if (womenSliderContainer && womenSliderContainer.classList.contains('is-active') && womenPaginationEl) {
                var activeBullet = womenPaginationEl.querySelector('.swiper-pagination-bullet-active');
                if (activeBullet) {
                  womenInitialized = true;
                  // 初回のみ適切な長さのアニメーション(6.5秒)
                  animatePaginationSmooth(activeBullet, 6500);
                }
              }
            }, 300);
          },
          autoplayStart: function() {
            // autoplay開始と同時にアニメーション開始(初期化済みの場合はスキップ)
            if (!womenInitialized && womenPaginationEl) {
              var activeBullet = womenPaginationEl.querySelector('.swiper-pagination-bullet-active');
              if (activeBullet) {
                animatePaginationSmooth(activeBullet, SLIDER_AUTOPLAY_DELAY);
              }
            }
          },
          slideChangeTransitionStart: function() {
            // スライド切り替え時のページネーションアニメーション
            if (womenPaginationEl) {
              // すべてのページネーションアイコンをリセット
              var allBullets = womenPaginationEl.querySelectorAll('.swiper-pagination-bullet');
              allBullets.forEach(function(bullet) {
                bullet.style.background = '';
              });
              
              // アクティブなアイコンのアニメーション開始
              var activeBullet = womenPaginationEl.querySelector('.swiper-pagination-bullet-active');
              if (activeBullet) {
                animatePaginationSmooth(activeBullet, SLIDER_AUTOPLAY_DELAY);
              }
            }
          }
        }
      });
    }
  }
  
  // Men用スライダーを初期化
  var menSliderEl = document.querySelector('.js-slider-men');
  var menPaginationEl = document.querySelector('.js-slider-pagination-men');
  if (menSliderEl && typeof Swiper !== 'undefined') {
    var menSlides = menSliderEl.querySelectorAll('.swiper-slide');
    if (menSlides.length > 0) {
      menSlider = new Swiper(menSliderEl, {
        speed: 900,
        slidesPerView: 1,
        loop: menSlides.length > 1,
        threshold: 10,
        autoplay: menSlides.length > 1 ? {
          delay: SLIDER_AUTOPLAY_DELAY,
          disableOnInteraction: false,
          pauseOnMouseEnter: false
        } : false,
        pagination: {
          el: menPaginationEl,
          clickable: true
        },
        on: {
          init: function() {
            // 初期化時にアクティブなスライダーのみアニメーション開始(短めのアニメーション)
            setTimeout(function() {
              var menSliderContainer = document.querySelector('.slider-men');
              if (menSliderContainer && menSliderContainer.classList.contains('is-active') && menPaginationEl) {
                var activeBullet = menPaginationEl.querySelector('.swiper-pagination-bullet-active');
                if (activeBullet) {
                  menInitialized = true;
                  // 初回のみ適切な長さのアニメーション(6.5秒)
                  animatePaginationSmooth(activeBullet, 6500);
                }
              }
            }, 300);
          },
          autoplayStart: function() {
            // autoplay開始と同時にアニメーション開始(初期化済みの場合はスキップ)
            if (!menInitialized && menPaginationEl) {
              var activeBullet = menPaginationEl.querySelector('.swiper-pagination-bullet-active');
              if (activeBullet) {
                animatePaginationSmooth(activeBullet, SLIDER_AUTOPLAY_DELAY);
              }
            }
          },
          slideChangeTransitionStart: function() {
            // スライド切り替え時のページネーションアニメーション
            if (menPaginationEl) {
              // すべてのページネーションアイコンをリセット
              var allBullets = menPaginationEl.querySelectorAll('.swiper-pagination-bullet');
              allBullets.forEach(function(bullet) {
                bullet.style.background = '';
              });
              
              // アクティブなアイコンのアニメーション開始
              var activeBullet = menPaginationEl.querySelector('.swiper-pagination-bullet-active');
              if (activeBullet) {
                animatePaginationSmooth(activeBullet, SLIDER_AUTOPLAY_DELAY);
              }
            }
          }
        }
      });
    }
  }
}

document.addEventListener('DOMContentLoaded', function() {
  // スライダーを初期化
  initializeSliders();
  
  // URLパラメータを優先し、なければCookieから復元
  var urlParams = new URLSearchParams(window.location.search);
  var urlGender = urlParams.get('gender');
  
  var currentGender;
  if (urlGender && (urlGender === 'women' || urlGender === 'men')) {
    // URLパラメータがある場合はそれを優先
    currentGender = urlGender;
    // URLパラメータの状態もCookieに保存
    saveGenderState(currentGender);
  } else {
    // URLパラメータがない場合はCookieから復元
    currentGender = restoreGenderState();
  }
  
  // ページ全体のgender表示を更新
  updateGenderDisplay(currentGender);

  // ジェンダーボタンのクリックイベント
  var genderBtns = document.querySelectorAll('.gender-btn');
  genderBtns.forEach(function(btn) {
    btn.addEventListener('click', function(e) {
      e.preventDefault();
      
      var gender = this.getAttribute('data-gender');
      
      // ページ全体の表示を更新
      updateGenderDisplay(gender);
      
      // 状態をCookieに保存
      saveGenderState(gender);
    });
  });
});

// ページ読み込み完了後に再度表示を更新(他のセクションの読み込み完了を待つ)
window.addEventListener('load', function() {
  // スライダーが正しく初期化されていない場合は再初期化
  if (!womenSlider || !menSlider) {
    initializeSliders();
  }
  
  var currentGender = restoreGenderState();
  updateGenderDisplay(currentGender);
  
  // Safari低電力モード検出
  detectLowPowerMode();
});

// Safari低電力モード検出関数(シンプル版)
function detectLowPowerMode() {
  // Safari検出
  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
  if (!isSafari) return;
  
  // 全ての動画要素を取得
  const videos = document.querySelectorAll('.c-slider-hero__video');
  
  videos.forEach(function(video) {
    // autoplay試行
    const playPromise = video.play();
    
    if (playPromise !== undefined) {
      playPromise.catch(function(error) {
        // autoplay失敗 = 低電力モード
        document.body.classList.add('safari-low-power-mode');
      });
    }
    
    // 1秒後に再生状態をチェック
    setTimeout(function() {
      if (video.paused && video.currentTime === 0) {
        // 再生されていない = 低電力モード
        document.body.classList.add('safari-low-power-mode');
      }
    }, 1000);
  });
}

</script>

{% schema %}
{
  "name": "t:sections.slideshow.name",
  "tag": "section",
  "class": "section",
  "disabled_on": {
    "groups": ["header", "footer"]
  },
  "settings": [
    {
      "type": "header",
      "content": "t:sections.slideshow.settings.accessibility.content"
    },
    {
      "type": "text",
      "id": "accessibility_info",
      "label": "t:sections.slideshow.settings.accessibility.label",
      "info": "t:sections.slideshow.settings.accessibility.info",
      "default": "Slideshow about our brand"
    }
  ],
  "blocks": [
    {
      "type": "slide",
      "name": "t:sections.slideshow.blocks.slide.name",
      "limit": 10,
      "settings": [
        {
          "type": "header",
          "content": "WOMEN用スライド設定"
        },
        {
          "type": "paragraph",
          "content": "Women用スライダーに表示される内容を設定します。画像が設定されていない場合、このスライドはWomen用スライダーに表示されません。"
        },
        {
          "type": "image_picker",
          "id": "image",
          "label": "画像(PC・タブレット用)"
        },
        {
          "type": "image_picker",
          "id": "image_sp",
          "label": "画像(スマートフォン用)"
        },
        {
          "type": "richtext",
          "id": "heading",
          "default": "<p>Image slide</p>",
          "label": "見出しテキスト"
        },
        {
          "type": "text",
          "id": "button_label",
          "default": "くわしく見る",
          "label": "ボタンテキスト",
          "info": "ボタンに表示されるテキストを入力してください"
        },
        {
          "type": "url",
          "id": "link",
          "label": "リンク先URL"
        },
        {
          "type": "video",
          "id": "video",
          "label": "動画(PC・タブレット用)",
          "info": "画像の代わりに動画を表示します。横長サイズ推奨(16:9)"
        },
        {
          "type": "video",
          "id": "video_sp",
          "label": "動画(スマートフォン用)",
          "info": "スマートフォン用の動画。設定しない場合はPC用と同じ動画が表示されます。縦長サイズ推奨(9:16)"
        },
        {
          "type": "header",
          "content": "MEN用スライド設定"
        },
        {
          "type": "paragraph",
          "content": "Men用スライダーに表示される内容を設定します。画像が設定されていない場合、このスライドはMen用スライダーに表示されません。"
        },
        {
          "type": "image_picker",
          "id": "image_men",
          "label": "画像(PC・タブレット用)"
        },
        {
          "type": "image_picker",
          "id": "image_sp_men",
          "label": "画像(スマートフォン用)"
        },
        {
          "type": "richtext",
          "id": "heading_men",
          "default": "<p>Image slide</p>",
          "label": "見出しテキスト"
        },
        {
          "type": "text",
          "id": "button_label_men",
          "default": "くわしく見る",
          "label": "ボタンテキスト",
          "info": "ボタンに表示されるテキストを入力してください"
        },
        {
          "type": "url",
          "id": "link_men",
          "label": "リンク先URL"
        },
        {
          "type": "video",
          "id": "video_men",
          "label": "動画(PC・タブレット用)",
          "info": "画像の代わりに動画を表示します。横長サイズ推奨(16:9)"
        },
        {
          "type": "video",
          "id": "video_sp_men",
          "label": "動画(スマートフォン用)",
          "info": "スマートフォン用の動画。設定しない場合はPC用と同じ動画が表示されます。縦長サイズ推奨(9:16)"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "t:sections.slideshow.presets.name",
      "blocks": [
        {
          "type": "slide"
        },
        {
          "type": "slide"
        }
      ]
    }
  ]
}
{% endschema %}

swiperのセットを読みこませるのを忘れずに。

【補足】修正後の要点の解説

修正前はswiperスライダーが1つでしたが、WOMEN用とMEN用でスライダーを2つに分けたということが大きな違いです。

html

<!-- Women用スライダー -->
<div class="p-top-hero__slider slider-women swiper js-slider-women is-active">
  <div class="swiper-wrapper">
    {%- for block in section.blocks -%}<!-- 👈 WOMEN用画ループ -->
    {%- if block.settings.image != blank -%}<!-- 👈 WOMEN用画像があるかチェック -->
      <div class="swiper-slide">
        <!-- Women用コンテンツ -->
      </div>
    {%- endif -%}
    {%- endfor -%}
  </div>
</div>

<!-- Men用スライダー -->
<div class="p-top-hero__slider slider-men swiper js-slider-men">
  <div class="swiper-wrapper">
    {%- for block in section.blocks -%}<!-- 👈 MEN用ループ -->
    {%- if block.settings.image_men != blank -%}<!-- 👈 MEN用画像があるかチェック -->
      <div class="swiper-slide">
        <!-- Men用コンテンツ -->
      </div>
    {%- endif -%}
    {%- endfor -%}
  </div>
</div>

男女のswiperスライダーそれぞれに、forループをしています。

振り分けのテクニック

Shopifyの制約で「ブロックが1階層のみ」という仕様です。2階層目(ネストしたブロック)はできません。このことから、同じブロックデータを2回ループして、異なる条件で振り分けしています。

管理画面のテンプレート上では変更ありません。
1つのブロックに男女の1セット登録ができます。

Shopifyの制約

  • セクションブロックは1階層のみ
  • ネストしたブロック構造は作れない
  • Liquidテンプレートでの処理が前提

以下、JSON(Schema)→ Liquid → HTML の流れです。

1. Schema(JSON)での定義

{
  "settings": [
    {
      "type": "image_picker",
      "id": "image",           // ← WOMEN用画像のID
      "label": "画像(PC・タブレット用)"
    },
    {
      "type": "image_picker", 
      "id": "image_men",       // ← MEN用画像のID
      "label": "画像(PC・タブレット用)"
    }
  ]
}

2. 管理画面で登録されるデータ構造

// Shopify内部でこんなJSONデータが生成される
section.blocks = [
  {
    "id": "block1",
    "settings": {
      "image": "women_slide1.jpg",      // WOMEN用画像
      "image_men": "men_slide1.jpg",    // MEN用画像
      "heading": "WOMEN'S COLLECTION",
      "heading_men": "MEN'S COLLECTION"
    }
  },
  {
    "id": "block2", 
    "settings": {
      "image": "women_slide2.jpg",      // WOMEN用のみ設定
      "image_men": null,                // MEN用は未設定
      "heading": "SUMMER LIMITED",
      "heading_men": null
    }
  }
]

このjsonを読み取ってLiquidで振り分けです。

3. Liquidでの振り分け処理

<!-- 1回目:WOMEN用フィルタリング -->
{%- for block in section.blocks -%}
  {%- if block.settings.image != blank -%}
    <!-- WOMEN用スライド生成 -->
  {%- endif -%}
{%- endfor -%}

<!-- 2回目:MEN用フィルタリング -->
{%- for block in section.blocks -%}
  {%- if block.settings.image_men != blank -%}
    <!-- MEN用スライド生成 -->
  {%- endif -%}
{%- endfor -%}

!= blankで画像があるかどうか見ています。

4. 実際の振り分け結果

2つ目のスライダー(block2)にMENを登録しなかった場合。
以下のような結果になると成功です。

想定出力(妄想)

<!--WOMEN用スライダー-->
<div class="js-slider-women">
<div class="swiper-wrapper">
<!-- block1: image があるので表示 -->
<div class="swiper-slide">
<img src="women_slide1.jpg">
<h2>WOMEN'S COLLECTION</h2>
</div>
<!-- block2: image があるので表示 -->
<div class="swiper-slide">
<img src="women_slide2.jpg">
<h2>SUMMER LIMITED</h2>
</div>
</div>
</div>

<!--MEN用スライダー-->
<div class="js-slider-men">
<div class="swiper-wrapper">
<!-- block1: image_men があるので表示 -->
<div class="swiper-slide">
<img src="men_slide1.jpg">
<h2>MEN'S COLLECTION</h2>
</div>
<!-- block2: image_men が null なので表示されない -->
</div>
</div>

方法は他にもあるかもしれませんが。
以上になります。

【AI】イラストを描いてもらった

今回の記事のキャッチ画像で使わせてもらいます「Google ImageFX」で作成した画像です。誰でもgoogleアカウントでログインして使えます。

この記事にピッタリなイラストのための考えたリクエストは、
「遺跡の祭壇でクリスタルに触れた時、眩しい光と共に目の前に、過去と未来の出来事が無限スクロールするように流れる様に驚く探検家。スクロール映像に寄りで、探検家は上半身の画角。探検家を男性に。」です。

トゥームレイダーみたいなの好きです。

星間旅路のメロディ

「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」

「この電波はどこの星からきたのだろうか。」

この惑星の夏は長かったようです。

局地的な現象かもしれませんね。