/* ========================================
キタムラ会員サービス 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",
},
});
};