Newer
Older
koeki-zero_website / js / main.js
/* ===============================================================
 * debounce関数
 * =============================================================== */
function debounce(func, wait) {
  let timeout;
  return function (...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}

/* ===============================================================
 * ユーティリティ関数(show/hide/visible 判定)
 * =============================================================== */
function showBlock(el) {
  if (!el) return;
  el.style.display = 'block';
}
function hide(el) {
  if (!el) return;
  el.style.display = 'none';
}
function isVisible(el) {
  if (!el) return false;
  return el.offsetParent !== null && getComputedStyle(el).display !== 'none';
}
function hideAllDropdowns() {
  document.querySelectorAll('.ddmenu_parent > ul').forEach(ul => hide(ul));
}

/* ===============================================================
 * メニュー関連(ロード・リサイズ)
 * =============================================================== */
const menubar = document.getElementById('menubar');
const menubarHdr = document.getElementById('menubar_hdr');

const onResponsive = debounce(function () {
  if (window.innerWidth < 900) { // ブレイクポイント
    document.body.classList.add('small-screen');
    document.body.classList.remove('large-screen');

    if (menubar) {
      menubar.classList.add('display-none');
      menubar.classList.remove('display-block');
    }
    if (menubarHdr) {
      menubarHdr.classList.remove('display-none', 'ham');
      menubarHdr.classList.add('display-block');
    }
  } else {
    document.body.classList.add('large-screen');
    document.body.classList.remove('small-screen');

    if (menubar) {
      menubar.classList.add('display-block');
      menubar.classList.remove('display-none');
    }
    if (menubarHdr) {
      menubarHdr.classList.remove('display-block');
      menubarHdr.classList.add('display-none');
      menubarHdr.classList.remove('ham'); // 念のため
    }

    // ドロップダウンメニューが開いていれば、それを閉じる
    hideAllDropdowns();
  }
}, 10);

window.addEventListener('load', onResponsive);
window.addEventListener('resize', onResponsive);

/* ===============================================================
 * DOMContentLoaded 後の初期化
 * =============================================================== */
document.addEventListener('DOMContentLoaded', function () {
  /* ---------------------------
   * ハンバーガーメニュークリック
   * --------------------------- */
  if (menubarHdr) {
    menubarHdr.addEventListener('click', function () {
      this.classList.toggle('ham');
      if (this.classList.contains('ham')) {
        if (menubar) menubar.classList.add('display-block');
      } else {
        if (menubar) menubar.classList.remove('display-block');
      }
    });
  }

  /* ---------------------------
   * アンカーリンク(#を含む)でメニューを閉じる
   * --------------------------- */
  if (menubar) {
    menubar.querySelectorAll('a[href*="#"]').forEach(a => {
      a.addEventListener('click', function () {
        menubar.classList.remove('display-block');
        if (menubarHdr) menubarHdr.classList.remove('ham');
      });
    });

    /* ---------------------------
     * href=""(空リンク)を無効化
     * --------------------------- */
    menubar.querySelectorAll('a[href=""]').forEach(a => {
      a.addEventListener('click', function (e) {
        e.preventDefault();
      });
    });

    /* ---------------------------
     * ドロップダウンの親liタグ判定 & クラス付与
     * --------------------------- */
    menubar.querySelectorAll('li').forEach(li => {
      const directUl = li.querySelector(':scope > ul');
      if (directUl) {
        li.classList.add('ddmenu_parent');
        const directA = li.querySelector(':scope > a');
        if (directA) directA.classList.add('ddmenu');
      }
    });
  }

  /* ---------------------------
   * タッチデバイス用ドロップダウン
   * --------------------------- */
  let touchStartY = 0;

  document.querySelectorAll('.ddmenu').forEach(dd => {
    dd.addEventListener('touchstart', function (e) {
      const t = e.touches && e.touches[0];
      touchStartY = t ? t.clientY : 0;
    }, { passive: true });

    dd.addEventListener('touchend', function (e) {
      const t = e.changedTouches && e.changedTouches[0];
      const touchEndY = t ? t.clientY : 0;
      const diff = touchStartY - touchEndY;

      // スクロールでない(差分が小さい)場合のみドロップダウン制御
      if (Math.abs(diff) < 10) {
        const nextEl = this.nextElementSibling;
        if (nextEl && nextEl.tagName.toLowerCase() === 'ul') {
          if (isVisible(nextEl)) {
            hide(nextEl);
          } else {
            showBlock(nextEl);
          }
        }

        // 他のドロップダウンを閉じる
        document.querySelectorAll('.ddmenu').forEach(other => {
          if (other !== this) {
            const ul = other.nextElementSibling;
            if (ul && ul.tagName.toLowerCase() === 'ul') hide(ul);
          }
        });

        e.preventDefault(); // リンクフォロー防止
      }
    });
  });

  /* ---------------------------
   * PC用(hover)
   * --------------------------- */
  document.querySelectorAll('.ddmenu_parent').forEach(li => {
    li.addEventListener('mouseenter', function () {
      const ul = this.querySelector(':scope > ul');
      showBlock(ul);
    });
    li.addEventListener('mouseleave', function () {
      const ul = this.querySelector(':scope > ul');
      hide(ul);
    });
  });

  /* ---------------------------
   * ドロップダウン配下のリンククリックで閉じる
   * --------------------------- */
  document.querySelectorAll('.ddmenu_parent ul a').forEach(a => {
    a.addEventListener('click', function () {
      hideAllDropdowns();
    });
  });

  /* ===============================================================
   * 小さなメニューが開いている間、bodyスクロール禁止
   * =============================================================== */
  function toggleBodyScroll() {
    const hdr = menubarHdr;
    if (!hdr) return;
    const isHam = hdr.classList.contains('ham');
    const isHidden = hdr.classList.contains('display-none');

    if (isHam && !isHidden) {
      document.body.style.overflow = 'hidden';
      document.body.style.height = '100%';
    } else {
      document.body.style.overflow = '';
      document.body.style.height = '';
    }
  }

  toggleBodyScroll(); // 初期チェック
  if (menubarHdr) {
    const observer = new MutationObserver(toggleBodyScroll);
    observer.observe(menubarHdr, { attributes: true, attributeFilter: ['class'] });
  }

  /* ===============================================================
   * スムーススクロール(通常タイプ)
   * =============================================================== */
  const topButtons = document.querySelectorAll('.pagetop');
  const scrollShowClass = 'pagetop-show';

  function smoothScroll(target) {
    let top = 0;
    if (target !== '#') {
      const el = document.querySelector(target);
      if (el) {
        const rect = el.getBoundingClientRect();
        top = rect.top + window.pageYOffset;
      }
    }
    window.scrollTo({ top, behavior: 'smooth' });
  }

  // ページ内リンク
  document.querySelectorAll('a[href^="#"]').forEach(a => {
    a.addEventListener('click', function (e) {
      e.preventDefault();
      const id = this.getAttribute('href') || '#';
      smoothScroll(id);
    });
  });

  // ページトップボタン
  topButtons.forEach(btn => {
    btn.style.display = 'none'; // 初期は非表示
    btn.addEventListener('click', function (e) {
      e.preventDefault();
      smoothScroll('#');
    });
  });

  window.addEventListener('scroll', function () {
    const shouldShow = window.scrollY >= 300;
    topButtons.forEach(btn => {
      if (shouldShow) {
        btn.style.display = ''; // CSSに任せる(または 'block')
        btn.classList.add(scrollShowClass);
      } else {
        btn.style.display = 'none';
        btn.classList.remove(scrollShowClass);
      }
    });
  });

  // ハッシュ付きでロードされたときの処理
  if (window.location.hash) {
    window.scrollTo(0, 0);
    setTimeout(() => {
      smoothScroll(window.location.hash);
    }, 10);
  }

  /* ===============================================================
   * 汎用開閉処理
   * =============================================================== */
  document.querySelectorAll('.openclose').forEach(trigger => {
    const target = trigger.nextElementSibling;
    if (target) hide(target); // 初期で閉じる

    trigger.addEventListener('click', function () {
      // 自分の次要素をトグル
      if (target) {
        if (isVisible(target)) {
          hide(target);
        } else {
          showBlock(target);
        }
      }

      // 他の .openclose は閉じる
      document.querySelectorAll('.openclose').forEach(other => {
        if (other !== this) {
          const otherTarget = other.nextElementSibling;
          if (otherTarget) hide(otherTarget);
        }
      });
    });
  });
});