モーダルメニュー内にアコーディオンを配置する

Filed under: css,JavaScript — kdcs @ 26年3月31日 火曜日

多くの選択肢をモーダルメニューに入れるとメニューが縦長になりすぎてしまうのでアコーディオン化して閉じておく処理

例は、ブランド一覧
html

<?php /* gn ブランド一覧ここから---------------------------------------------------------- */ ?>
<div class="gnBrandList">
<button class="gnBrandBtn">BRAND LIST</button>
<ul class="gnBrandMenu">
<li><a href="<?php echo home_url(); ?>/items_cat/cafering" title="CAFERING">CAFERING</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/cafering_k" title="CAFERING K">CAFERING K</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/hoshinosuna" title="HOSHInoSUNA">HOSHInoSUNA 星の砂</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/belethe" title="BELETHE">BELETHE</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/k_uno" title="disney-treasure">K.UNO "Disney Treasure"</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/sakura_diamond" title="さくらダイヤモンド">さくらダイヤモンド</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/fika" title="fika">fika</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/lapage" title="LAPAGE">LAPAGE</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/nina_ricci" title="NINA RICCI">NINA RICCI</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/private_beach" title="ハワイアンジュエリー「プライベートビーチ」">private beach</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/lor" title="L'or">L'or</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/ptau" title="ptau">ptau</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/truelove" title="True Love">True Love</a></li>
<li><a href="<?php echo home_url(); ?>/items_cat/fujita_original_selection" title="Fujita Original Selection">Fujita Original Selection</a></li>
</ul>
</div><!--/.gnBrandList-->
<?php /* gn ブランド一覧ここまで---------------------------------------------------------- */ ?>

css

/* ▼アコーディオン --------------------*/
.gnBrandList {
    width: 100%;
    margin-bottom: 20px;
}
/* 初期状態は閉じる */
.gnBrandMenu {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
    padding-left: 0;
    margin: 0;
}
.gnBrandMenu li {
    list-style: none;
}
.gnBrandMenu li a {
    display: block;
    padding: 8px 10px;
    border-bottom: 1px solid #ddd;
    text-decoration: none;
    color: #333;
}
.gnBrandBtn {
    width: 100%;
    padding: 10px;
    background: #937a6b;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 16px;
    position: relative;
}
/* ▼ の矢印を疑似要素で作る */
.gnBrandBtn::after {
    content: "▼";
    position: absolute;
    right: 15px;
    top: 50%;
    transform: translateY(-50%);
    transition: transform 0.3s ease;
}
/* 開いた時に矢印を回転 */
.gnBrandBtn.active::after {
    transform: translateY(-50%) rotate(180deg);
}
/* アコーディオン本体 */
.gnBrandMenu {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.6s ease;
    padding-left: 0;
    margin: 0;
}

javascript

// アコーディオン
document.querySelector('.gnBrandBtn').addEventListener('click', function() {
  const menu = document.querySelector('.gnBrandMenu');

  if (menu.classList.contains('open')) {
    // 閉じるとき
    menu.style.maxHeight = null;
  } else {
    // 開くとき:中身の高さを自動で取得
    menu.style.maxHeight = menu.scrollHeight + "px";
  }

  menu.classList.toggle('open');
  this.classList.toggle('active'); // ← 矢印回転
});

【重要】モーダルメニューを閉じるときにアコーディオンを閉じた状態にしたいのでモーダルメニュー開閉のjavascriptに「モーダル閉じたらアコーディオンも閉じる」を組み込んだコードにする必要がある。

こちらが組み込んだモーダルのjs

// 新スマホ グローバルメニュー -------------------------------------------------

var trigger = document.querySelector('.globalMenu');
var closeButtons = document.querySelectorAll('.closeButton');
var body = document.body;
var modal = document.getElementById('globalNavArea');
let scrollPosition = 0;

// ★ アコーディオンを閉じる処理(チラつき防止付き)
function resetAccordion() {
  const menu = document.querySelector('.gnBrandMenu');
  const btn = document.querySelector('.gnBrandBtn');

  if (!menu || !btn) return;

  // ★ transition を一時的に無効化(チラつき防止)
  menu.style.transition = 'none';

  // アコーディオンを閉じる
  menu.classList.remove('open');
  btn.classList.remove('active');
  menu.style.maxHeight = null;

  // ★ 次のフレームで transition を元に戻す
  requestAnimationFrame(() => {
    menu.style.transition = '';
  });
}

// モーダルを閉じる処理
function closeModal() {
  trigger.classList.remove('is-active');
  body.classList.remove('is-nav-opened');
  body.classList.add('is-nav-close');

  body.style.top = '';
  window.scrollTo(0, scrollPosition);

  // ★ モーダルを閉じたらアコーディオンも閉じる
  resetAccordion();
}

// モーダルを開く処理
function openModal() {
  scrollPosition = window.scrollY;

  trigger.classList.add('is-active');

  if (body.classList.contains('is-nav-close')) {
    body.classList.remove('is-nav-close');
  }

  body.classList.add('is-nav-opened');

  body.style.top = `-${scrollPosition}px`;
  modal.scrollTop = 0;
}

// トリガークリック
trigger.addEventListener('click', function () {
  if (body.classList.contains('is-nav-opened')) {
    closeModal();
  } else {
    openModal();
  }
});

// closeButton クリックでも閉じる
closeButtons.forEach(btn => {
  btn.addEventListener('click', closeModal);
});

スマホでモーダルメニューを開閉する時にアコーディオンをリセット(閉じ状態)する

Filed under: JavaScript — kdcs @ 26年3月31日 火曜日

モーダルメニューの中でアコーディオンを使う時、アコーディオンを開いた状態でメニューを閉じて再度モーダルメニューを開いたときにアコーディオンを閉じた状態にしたいので、モーダルメニュー開閉にアコーディオンのリセットを組み込んだコード。

var trigger = document.querySelector('.globalMenu');
var closeButtons = document.querySelectorAll('.closeButton');
var body = document.body;
var modal = document.getElementById('globalNavArea');
let scrollPosition = 0;

// ★ アコーディオンを閉じる処理(チラつき防止付き)
function resetAccordion() {
  const menu = document.querySelector('.gnBrandMenu');
  const btn = document.querySelector('.gnBrandBtn');

  if (!menu || !btn) return;

  // ★ transition を一時的に無効化(チラつき防止)
  menu.style.transition = 'none';

  // アコーディオンを閉じる
  menu.classList.remove('open');
  btn.classList.remove('active');
  menu.style.maxHeight = null;

  // ★ 次のフレームで transition を元に戻す
  requestAnimationFrame(() => {
    menu.style.transition = '';
  });
}

// モーダルを閉じる処理
function closeModal() {
  trigger.classList.remove('is-active');
  body.classList.remove('is-nav-opened');
  body.classList.add('is-nav-close');

  body.style.top = '';
  window.scrollTo(0, scrollPosition);

  // ★ モーダルを閉じたらアコーディオンも閉じる(チラつきなし)
  resetAccordion();
}

// モーダルを開く処理
function openModal() {
  scrollPosition = window.scrollY;

  trigger.classList.add('is-active');

  if (body.classList.contains('is-nav-close')) {
    body.classList.remove('is-nav-close');
  }

  body.classList.add('is-nav-opened');

  body.style.top = `-${scrollPosition}px`;
  modal.scrollTop = 0;
}

// トリガークリック
trigger.addEventListener('click', function () {
  if (body.classList.contains('is-nav-opened')) {
    closeModal();
  } else {
    openModal();
  }
});

// closeButton クリックでも閉じる
closeButtons.forEach(btn => {
  btn.addEventListener('click', closeModal);
});

アコーディオンをモーダルメニューの中で使わないときのコード

var trigger = document.querySelector('.globalMenu');
var closeButtons = document.querySelectorAll('.closeButton');
var body = document.body;
var modal = document.getElementById('globalNavArea');
let scrollPosition = 0;

// モーダルを閉じる処理
function closeModal() {
  trigger.classList.remove('is-active');
  body.classList.remove('is-nav-opened');
  body.classList.add('is-nav-close');

  body.style.top = '';
  window.scrollTo(0, scrollPosition);
}

// モーダルを開く処理
function openModal() {
  scrollPosition = window.scrollY;

  trigger.classList.add('is-active');

  if (body.classList.contains('is-nav-close')) {
    body.classList.remove('is-nav-close');
  }

  body.classList.add('is-nav-opened');

  body.style.top = `-${scrollPosition}px`;
  modal.scrollTop = 0;
}

// トリガークリック
trigger.addEventListener('click', function () {
  if (body.classList.contains('is-nav-opened')) {
    closeModal();
  } else {
    openModal();
  }
});

// closeButton クリックでも閉じる
closeButtons.forEach(btn => {
  btn.addEventListener('click', closeModal);
});

jQueryを使わないデートピッカー「flatpickr」

Filed under: JavaScript — kdcs @ 26年3月2日 月曜日

jQueryを使わないウェブサイトを構築するための手法「デートピッカー編」

flatpickr

html

<link rel="stylesheet" href="flatpickr.min.css">
<script src="flatpickr.min.js"></script>

<input id="date" type="text">

設定例
・毎週の休日:土日を無効化
・個別の休日:配列で指定
・第1・第3・第5土曜日を休日:disableに関数を渡して判定

// 個別の休日(文字列 or Date)
const specificHolidays = [
  "2026-03-21",
  "2026-04-29",
  new Date(2026, 4, 3) // 2026-05-03
];

// 第1・第3・第5土曜日かどうか判定する関数
function isNthSaturday(date) {
  const day = date.getDay();      // 0:日〜6:土
  if (day !== 6) return false;    // 土曜以外は対象外

  const d = date.getDate();       // 日にち
  const nth = Math.floor((d - 1) / 7) + 1; // 第何週か(1〜5)
  return nth === 1 || nth === 3 || nth === 5;
}

flatpickr("#date", {
  dateFormat: "Y-m-d",
  disable: [
    // 個別の休日
    ...specificHolidays,

    // 関数で動的に無効化
    function(date) {
      const day = date.getDay();

      // 毎週の休日(例:日曜=0, 土曜=6)
      if (day === 0 || day === 6) return true;

      // 第1・第3・第5土曜日を休日にする
      if (isNthSaturday(date)) return true;

      // それ以外は有効
      return false;
    }
  ]
});

Swiperでスライド要素が1つの時、カスタムナビゲーションを非表示にする

Filed under: JavaScript,swiper — kdcs @ 25年11月6日 木曜日

swiperでページネーションやナビゲーションボタンを.swiperの外側に出して表示位置をカスタマイズしているとき、スライド要素が1つしかない場合はナビゲーションを非表示にしておきたい。

以下のオプション設定で、スライド要素の数を確認し、1つの場合はstyleでdisplay:noe;を付けて非表示にする。
※基本的には、’#eventAd .custom-navigation’これだけをdisplay:noneにすればよい

const swiper2 = new Swiper('.eventAdSlider', {
  loop: true,
  parallax: true, // パララックスさせる
  allowTouchMove: false, // マウスでのスワイプを禁止
  speed: 1500,
  autoplay: {
    delay: 5000, // 5秒ごとに自動再生
    disableOnInteraction: false, // ユーザー操作後も自動再生を継続
  },
  pagination: {
    el: '#eventAd .swiper-pagination',
  },
  navigation: {
    nextEl: '#eventAd .swiper-button-next',
  },
  on: {
    init: function () {
// スライド要素が1つの場合、カスタムナビ非表示(loop:true の場合、クローン要素も含まれるので注意)
      const realSlideCount = this.slides.filter(slide => !slide.classList.contains('swiper-slide-duplicate')).length;
      if (realSlideCount <= 1) {
        document.querySelector('#eventAd .custom-navigation').style.display = 'none';
        document.querySelector('#eventAd .swiper-button-next').style.display = 'none';
        document.querySelector('#eventAd .swiper-button-prev').style.display = 'none';
        document.querySelector('#eventAd .swiper-pagination').style.display = 'none';
      }
    }
  }
});

非表示にする必要が無い場合はこちら

const swiper2 = new Swiper('.eventAdSlider', {
  loop: true,
  parallax: true, // パララックスさせる
  allowTouchMove: false, // マウスでのスワイプを禁止
  speed: 1500,
  autoplay: {
    delay: 5000, // 5秒ごとに自動再生
    disableOnInteraction: false, // ユーザー操作後も自動再生を継続
  },
  pagination: {
    el: '#eventAd .swiper-pagination',
  },
  navigation: {
    nextEl: '#eventAd .swiper-button-next',
  },
});

スマホでモーダルメニューを開閉する時にページ内での位置を保持させる

Filed under: JavaScript — kdcs @ 25年10月9日 木曜日

スマートフォンでページの途中でモーダルメニューを開閉したときにページ内での位置を保持して再表示させる方法。

※コード内でスクロール位置を先に取得して保存させないと、cssのスタイルが先に実行されて「top: 0px」になってしまい、メニューの開閉でページトップに戻されてしまう。

動作OKのコード(scrollアニメ表示版があります)

const trigger = document.querySelector('.globalMenu');
const body = document.body;
const modal = document.getElementById('globalNavArea');
let scrollPosition = 0;

const toggleNav = () => {
  const isOpening = !trigger.classList.contains('is-active'); // 開くかどうかを事前に判定

  if (isOpening) {
    scrollPosition = window.scrollY; // スクロール位置を先に取得
    console.log('保存する scrollPosition:', scrollPosition);
  }

  const isActive = trigger.classList.toggle('is-active');
  body.classList.toggle('is-nav-opened', isActive);
  body.classList.toggle('is-nav-close', !isActive);

  if (isActive) {
    body.style.top = `-${scrollPosition}px`;
    modal.scrollTop = 0;
  } else {
    body.style.top = '';
    window.scrollTo(0, scrollPosition);
  }
};

trigger.addEventListener('click', toggleNav);

こちらはモーダルウィンドウメニューを開いたときにscrollアニメを5秒間表示させるスクリプト
※メニュー開閉時のページ位置保持も修正されています

// 読み込み完了するまでグローバルメニューを非表示にする
// style.cssで「#globalNav」をdisplay: none;にしておく
window.onload = function () {
  document.getElementById('globalNav').style.display = 'block';

// スマホ グローバルメニュー(メニューを開くと5秒間scrollアニメを表示する) ----
const trigger = document.querySelector('.globalMenu');
const body = document.body;
const modal = document.getElementById('globalNavArea');
const scrollNotice = document.querySelector('.sp_block.gnScroll');
let scrollPosition = 0;

let fadeOutTimer = null;
let hideTimer = null;

const toggleNav = () => {
  const isOpening = !trigger.classList.contains('is-active');

  if (isOpening) {
    scrollPosition = window.scrollY;
    console.log('保存する scrollPosition:', scrollPosition);
  }

  const isActive = trigger.classList.toggle('is-active');
  body.classList.toggle('is-nav-opened', isActive);
  body.classList.toggle('is-nav-close', !isActive);

  if (isActive) {
    // スクロールを止める
    body.style.position = 'fixed';
    body.style.top = `-${scrollPosition}px`;
    body.style.left = '0';
    body.style.right = '0';
    modal.scrollTop = 0;

    clearTimeout(fadeOutTimer);
    clearTimeout(hideTimer);

    if (scrollNotice) {
      scrollNotice.style.display = 'block';
      scrollNotice.classList.remove('fade-out');

      fadeOutTimer = setTimeout(() => {
        scrollNotice.classList.add('fade-out');
      }, 5000);

      hideTimer = setTimeout(() => {
        scrollNotice.style.display = 'none';
        scrollNotice.classList.remove('fade-out');
      }, 5500);
    }
  } else {
    // スクロールを戻す
    body.style.position = '';
    body.style.top = '';
    body.style.left = '';
    body.style.right = '';
    window.scrollTo(0, scrollPosition);

    clearTimeout(fadeOutTimer);
    clearTimeout(hideTimer);

    if (scrollNotice) {
      scrollNotice.style.display = 'none';
      scrollNotice.classList.remove('fade-out');
    }
  }
};

trigger.addEventListener('click', toggleNav);

サイト内検索

カテゴリー

最近の投稿

↑上に戻る