/* ======================================== キタムラ会員サービス JavaScript ======================================== */ // DOM読み込み完了後に実行 document.addEventListener("DOMContentLoaded", function() { // スムーススクロール機能 initSmoothScroll(); // アニメーション機能 initAnimations(); // レスポンシブ対応 initResponsive(); // ボタンクリックイベント initButtonEvents(); }); /* ======================================== スムーススクロール機能 ======================================== */ function initSmoothScroll() { // 内部リンクのスムーススクロール const links = document.querySelectorAll("a[href^=\"#\"]"); links.forEach(link => { link.addEventListener("click", function(e) { e.preventDefault(); const targetId = this.getAttribute("href").substring(1); const targetElement = document.getElementById(targetId); if (targetElement) { targetElement.scrollIntoView({ behavior: "smooth", block: "start" }); } }); }); } /* ======================================== アニメーション機能 ======================================== */ function initAnimations() { // Intersection Observer を使用したスクロールアニメーション const observerOptions = { threshold: 0.1, rootMargin: "0px 0px -50px 0px" }; const observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add("animate-in"); } }); }, observerOptions); // アニメーション対象要素を監視 const animateElements = document.querySelectorAll( ".service__rank, .service__welcome-coupon, .service__stamp-card, " + ".guide__step, .notice__contact-item" ); animateElements.forEach(element => { element.classList.add("animate-element"); observer.observe(element); }); // フェードインアニメーションのCSS const style = document.createElement("style"); style.textContent = ` .animate-element { opacity: 0; transform: translateY(30px); transition: opacity 0.6s ease, transform 0.6s ease; } .animate-element.animate-in { opacity: 1; transform: translateY(0); } .mv__phone { transition: transform 0.3s ease; } .mv__phone:hover { transform: scale(1.05); } .btn { transition: all 0.3s ease; } .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } `; document.head.appendChild(style); } /* ======================================== レスポンシブ対応 ======================================== */ function initResponsive() { // ウィンドウリサイズ時の処理 let resizeTimer; window.addEventListener("resize", function() { clearTimeout(resizeTimer); resizeTimer = setTimeout(function() { handleResize(); }, 250); }); // 初期実行 handleResize(); } function handleResize() { const windowWidth = window.innerWidth; // スマートフォン表示時の調整 if (windowWidth <= 480) { adjustForMobile(); } else if (windowWidth <= 768) { adjustForTablet(); } else { adjustForDesktop(); } } function adjustForMobile() { // モバイル表示時の調整 const rankList = document.querySelector(".service__rank-list"); if (rankList) { rankList.style.gridTemplateColumns = "1fr"; } const stampStamps = document.querySelectorAll(".service__stamp-card-stamps"); stampStamps.forEach(stamps => { stamps.style.gridTemplateColumns = "repeat(2, 1fr)"; }); } function adjustForTablet() { // タブレット表示時の調整 const rankList = document.querySelector(".service__rank-list"); if (rankList) { rankList.style.gridTemplateColumns = "repeat(2, 1fr)"; } const stampStamps = document.querySelectorAll(".service__stamp-card-stamps"); stampStamps.forEach(stamps => { stamps.style.gridTemplateColumns = "repeat(3, 1fr)"; }); } function adjustForDesktop() { // デスクトップ表示時の調整 const rankList = document.querySelector(".service__rank-list"); if (rankList) { rankList.style.gridTemplateColumns = "repeat(5, 1fr)"; } const stampStamps = document.querySelectorAll(".service__stamp-card-stamps"); stampStamps.forEach(stamps => { if (stamps.classList.contains("service__stamp-card-stamps_mario")) { stamps.style.gridTemplateColumns = "repeat(6, 1fr)"; } else { stamps.style.gridTemplateColumns = "repeat(5, 1fr)"; } }); } /* ======================================== ボタンクリックイベント ======================================== */ function initButtonEvents() { // CTAボタンのクリックイベント const ctaButtons = document.querySelectorAll(".btn__theme-primary"); ctaButtons.forEach(button => { button.addEventListener("click", function(e) { const href = this.getAttribute("href"); const isAnchorTag = this.tagName === "A"; const isHashLink = isAnchorTag && href && href.startsWith("#"); const isNavigableLink = isAnchorTag && href && !isHashLink; // 通常のリンク(ハッシュ以外)はデフォルト遷移を許可 if (!isNavigableLink) { e.preventDefault(); } // ボタンクリック時のアニメーション this.style.transform = "scale(0.95)"; setTimeout(() => { this.style.transform = ""; }, 150); // 以外、またはハッシュリンクの場合のみアプリ側処理を継続 if (!isNavigableLink) { if (typeof handleRegistration === "function") { handleRegistration(); } } }); }); // リンクのクリックイベント const links = document.querySelectorAll(".service__link"); links.forEach(link => { link.addEventListener("click", function(e) { // デフォルトのリンク遷移を妨げない(ハッシュリンクのスムーススクロールはinitSmoothScrollに任せる) console.log("リンクがクリックされました:", this.textContent); }); }); } /* ======================================== 登録処理 ======================================== */ /*function handleRegistration() { // 登録処理の実装 console.log("会員登録処理を開始します"); // ローディング表示 showLoading(); // 実際の登録API呼び出し setTimeout(() => { hideLoading(); showSuccessMessage(); }, 2000); } function showLoading() { // ローディング表示の実装 const loading = document.createElement("div"); loading.className = "loading-overlay"; loading.innerHTML = `

登録処理中...

`; // ローディングスタイル const style = document.createElement("style"); style.textContent = ` .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; } .loading-content { background-color: #fff; padding: 2rem; border-radius: 1rem; text-align: center; } .loading-spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #d80b24; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1rem; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); document.body.appendChild(loading); } function hideLoading() { const loading = document.querySelector(".loading-overlay"); if (loading) { loading.remove(); } } function showSuccessMessage() { // 成功メッセージ表示 const message = document.createElement("div"); message.className = "success-message"; message.innerHTML = `

登録完了!

キタムラ会員への登録が完了しました。

`; // 成功メッセージスタイル const style = document.createElement("style"); style.textContent = ` .success-message { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; } .success-content { background-color: #fff; padding: 2rem; border-radius: 1rem; text-align: center; max-width: 400px; margin: 0 1rem; } .success-content h3 { color: #d80b24; margin-bottom: 1rem; } .success-content p { margin-bottom: 1.5rem; } `; document.head.appendChild(style); document.body.appendChild(message); }*/ /* ======================================== ユーティリティ関数 ======================================== */ // デバウンス関数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // スロットル関数 function throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // 要素の可視性チェック function isElementInViewport(el) { const rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } // スクロール位置取得 function getScrollPosition() { return window.pageYOffset || document.documentElement.scrollTop; } // 要素までスクロール function scrollToElement(element, offset = 0) { const elementPosition = element.offsetTop - offset; window.scrollTo({ top: elementPosition, behavior: "smooth" }); } /* ======================================== エラーハンドリング ======================================== */ window.addEventListener("error", function(e) { console.error("JavaScript エラー:", e.error); }); window.addEventListener("unhandledrejection", function(e) { console.error("未処理のPromise エラー:", e.reason); }); /* ======================================== パフォーマンス最適化 ======================================== */ // 画像の遅延読み込み function initLazyLoading() { const images = document.querySelectorAll("img[data-src]"); const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove("lazy"); imageObserver.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } // 初期化時に遅延読み込みを設定 document.addEventListener("DOMContentLoaded", function() { initLazyLoading(); }); /* ======================================== アクセシビリティ対応 ======================================== */ // キーボードナビゲーション対応 function initKeyboardNavigation() { document.addEventListener("keydown", function(e) { // Tab キーでフォーカス可能要素をハイライト if (e.key === "Tab") { document.body.classList.add("keyboard-navigation"); } }); document.addEventListener("mousedown", function() { document.body.classList.remove("keyboard-navigation"); }); } // 初期化時にキーボードナビゲーションを設定 document.addEventListener("DOMContentLoaded", function() { initKeyboardNavigation(); }); // フォーカススタイルの追加 const focusStyle = document.createElement("style"); focusStyle.textContent = ` .keyboard-navigation *:focus { outline: 2px solid #d80b24; outline-offset: 2px; } `; document.head.appendChild(focusStyle); //Swiper ---------------------------------------------------------------start /*const mySwiper = new Swiper(".service__warranty-steps", { loop: true, initialSlide: 2, slidesPerView: 'auto', slidesPerGroup: 1, spaceBetween:20, centeredSlides: true, loopAdditionalSlides: 2, watchSlidesVisibility: true, breakpoints: { 768: { slidesPerGroup: 1, spaceBetween:40, slidesPerView: 'auto', } }, direction: 'horizontal', loopPreventsSlide: false, disableOnInteraction: true, speed: 4000, init: false, autoplay: { delay: 0, reverseDirection: false, }, pagination: { el: ".swiper-pagination", clickable: true, }, }); mySwiper.on('init', function() { mySwiper.autoplay.start(); }); mySwiper.init();*/ const swiperSlides = document.getElementsByClassName(".carousel"); const breakPoint = 767; // ブレークポイントを設定 let swiper; let swiperBool; window.addEventListener( "load", () => { if (breakPoint < window.innerWidth) { swiperBool = false; } else { createSwiper(); swiperBool = true; } }, false ); window.addEventListener( "resize", () => { if (breakPoint < window.innerWidth && swiperBool) { swiper.destroy(false, true); swiperBool = false; } else if (breakPoint >= window.innerWidth && !swiperBool) { createSwiper(); swiperBool = true; } }, false ); const createSwiper = () => { swiper = new Swiper(".carousel", { loop: false, initialSlide: 0, slidesPerView: 'auto', slidesPerGroup: 1, spaceBetween:25, centeredSlides: true, //loopAdditionalSlides: 2, watchSlidesVisibility: true, direction: 'horizontal', loopPreventsSlide: false, disableOnInteraction: true, speed: 1500, /*autoplay: { delay: 0, },*/ pagination: { el: ".swiper-pagination", clickable: true, }, navigation: { nextEl: ".swiper-button-next", prevEl: ".swiper-button-prev", }, }); };