mingle-website/js/common.js
2025-09-14 00:24:42 +09:00

332 lines
9.9 KiB
JavaScript

// ========================================
// 공통 JavaScript 모듈
// ========================================
// DOM 로드 완료 후 실행
document.addEventListener('DOMContentLoaded', function() {
showPageLoading();
initializeNavigation();
setActiveNavLink();
loadComponents();
initLazyLoading();
hidePageLoading();
});
// ========================================
// 컴포넌트 로드 함수
// ========================================
async function loadComponents() {
// Header 로드
const headerPlaceholder = document.getElementById('header-placeholder');
if (headerPlaceholder) {
try {
const response = await fetch('components/header.html');
const html = await response.text();
headerPlaceholder.innerHTML = html;
initializeNavigation(); // 헤더 로드 후 네비게이션 초기화
} catch (error) {
console.error('Error loading header:', error);
}
}
// Footer 로드
const footerPlaceholder = document.getElementById('footer-placeholder');
if (footerPlaceholder) {
try {
const response = await fetch('components/footer.html');
const html = await response.text();
footerPlaceholder.innerHTML = html;
} catch (error) {
console.error('Error loading footer:', error);
}
}
}
// ========================================
// 네비게이션 기능
// ========================================
function initializeNavigation() {
const hamburger = document.querySelector('.hamburger');
const navMenu = document.querySelector('.nav-menu');
if (hamburger && navMenu) {
hamburger.addEventListener('click', function() {
hamburger.classList.toggle('active');
navMenu.classList.toggle('active');
});
// 네비게이션 링크 클릭 시 모바일 메뉴 닫기
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', () => {
hamburger.classList.remove('active');
navMenu.classList.remove('active');
});
});
}
// 스크롤 시 네비게이션 바 스타일 변경
let lastScroll = 0;
window.addEventListener('scroll', () => {
const navbar = document.querySelector('.navbar');
const currentScroll = window.pageYOffset;
if (navbar) {
if (currentScroll > 100) {
navbar.style.boxShadow = '0 2px 20px rgba(0, 0, 0, 0.1)';
} else {
navbar.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.05)';
}
}
lastScroll = currentScroll;
});
}
// ========================================
// 현재 페이지 네비게이션 링크 활성화
// ========================================
function setActiveNavLink() {
const currentPath = window.location.pathname;
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
const linkPath = link.getAttribute('href');
if (currentPath === linkPath ||
(currentPath === '/' && linkPath === '/') ||
(currentPath.includes(linkPath) && linkPath !== '/')) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
// ========================================
// 스크롤 애니메이션
// ========================================
function initScrollAnimations() {
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -100px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
}
});
}, observerOptions);
// 애니메이션 대상 요소 관찰
document.querySelectorAll('.animate-on-scroll').forEach(el => {
observer.observe(el);
});
}
// ========================================
// 유틸리티 함수
// ========================================
// 이메일 유효성 검사
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// 알림 표시
function showNotification(message, type = 'info') {
// 기존 알림 제거
const existingNotification = document.querySelector('.notification');
if (existingNotification) {
existingNotification.remove();
}
// 새 알림 생성
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
// 스타일 적용
notification.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
padding: 1rem 2rem;
border-radius: 10px;
color: white;
font-weight: 600;
z-index: 10000;
transform: translateX(400px);
transition: transform 0.3s ease;
`;
// 타입별 배경색
const bgColors = {
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
info: '#3b82f6'
};
notification.style.backgroundColor = bgColors[type] || bgColors.info;
document.body.appendChild(notification);
// 애니메이션
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
// 자동 제거
setTimeout(() => {
notification.style.transform = 'translateX(400px)';
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 300);
}, 3000);
}
// 디바운스 함수
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(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// ========================================
// Lazy Loading for YouTube iframes
// ========================================
function initLazyLoading() {
// YouTube iframe lazy loading
const lazyIframes = document.querySelectorAll('iframe[data-src]');
if ('IntersectionObserver' in window) {
const iframeObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const iframe = entry.target;
iframe.src = iframe.dataset.src;
iframe.removeAttribute('data-src');
iframeObserver.unobserve(iframe);
}
});
}, {
rootMargin: '50px'
});
lazyIframes.forEach(iframe => {
iframeObserver.observe(iframe);
});
} else {
// Fallback for older browsers
lazyIframes.forEach(iframe => {
iframe.src = iframe.dataset.src;
iframe.removeAttribute('data-src');
});
}
// Image lazy loading (if any)
const lazyImages = document.querySelectorAll('img[data-src]');
if ('IntersectionObserver' in window && lazyImages.length > 0) {
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imageObserver.unobserve(img);
}
});
}, {
rootMargin: '50px'
});
lazyImages.forEach(img => {
imageObserver.observe(img);
});
}
}
// ========================================
// Export 함수들 (다른 스크립트에서 사용 가능)
// ========================================
// ========================================
// 로딩 상태 관리
// ========================================
function showPageLoading() {
// 페이지 로딩 오버레이가 이미 있는지 확인
if (document.getElementById('pageLoadingOverlay')) return;
const loadingOverlay = document.createElement('div');
loadingOverlay.id = 'pageLoadingOverlay';
loadingOverlay.className = 'loading-overlay';
loadingOverlay.innerHTML = `
<div class="loading-spinner"></div>
<div class="loading-text">페이지를 불러오는 중...</div>
`;
document.body.appendChild(loadingOverlay);
}
function hidePageLoading() {
const loadingOverlay = document.getElementById('pageLoadingOverlay');
if (loadingOverlay) {
setTimeout(() => {
loadingOverlay.style.opacity = '0';
setTimeout(() => {
if (loadingOverlay.parentNode) {
loadingOverlay.remove();
}
}, 150);
}, 100); // 최소 0.1초 표시
}
}
function showComponentLoading(element, text = '로딩 중...') {
element.innerHTML = `
<div class="component-loading">
<div class="loading-spinner"></div>
<div class="loading-text">${text}</div>
</div>
`;
}
function hideComponentLoading(element, content) {
element.innerHTML = content;
}
// ========================================
// Export 함수들 (다른 스크립트에서 사용 가능)
// ========================================
window.commonUtils = {
showNotification,
isValidEmail,
debounce,
throttle,
initScrollAnimations,
initLazyLoading,
showPageLoading,
hidePageLoading,
showComponentLoading,
hideComponentLoading
};