/* ===============================================================
* 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);
}
});
});
});
});