365 lines
12 KiB
JavaScript
365 lines
12 KiB
JavaScript
// ========================================
|
|
// 공통 JavaScript 모듈
|
|
// ========================================
|
|
|
|
// DOM 로드 완료 후 실행
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
showPageLoading();
|
|
initializeNavigation();
|
|
setActiveNavLink();
|
|
loadComponents();
|
|
initLazyLoading();
|
|
createFloatingSNS();
|
|
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;
|
|
|
|
// 동적 푸터 로드 성공 시 백업 푸터 숨기기
|
|
const backupFooter = document.querySelector('footer[style*="background:#222"]');
|
|
if (backupFooter) {
|
|
backupFooter.style.display = 'none';
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
// ========================================
|
|
// 플로팅 SNS 버튼 생성
|
|
// ========================================
|
|
function createFloatingSNS() {
|
|
// 이미 존재하면 생성하지 않음
|
|
if (document.querySelector('.floating-sns')) return;
|
|
|
|
const snsContainer = document.createElement('div');
|
|
snsContainer.className = 'floating-sns';
|
|
snsContainer.innerHTML = `
|
|
<a href="https://www.youtube.com/@minglestudio_mocap" target="_blank" rel="noopener noreferrer" class="sns-btn sns-btn-youtube" title="YouTube">
|
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
|
|
</svg>
|
|
</a>
|
|
<a href="https://x.com/minglestudio" target="_blank" rel="noopener noreferrer" class="sns-btn sns-btn-x" title="X (Twitter)">
|
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
|
|
</svg>
|
|
</a>
|
|
`;
|
|
|
|
document.body.appendChild(snsContainer);
|
|
}
|
|
|
|
// ========================================
|
|
// Export 함수들 (다른 스크립트에서 사용 가능)
|
|
// ========================================
|
|
window.commonUtils = {
|
|
showNotification,
|
|
isValidEmail,
|
|
debounce,
|
|
throttle,
|
|
initScrollAnimations,
|
|
initLazyLoading,
|
|
showPageLoading,
|
|
hidePageLoading,
|
|
showComponentLoading,
|
|
hideComponentLoading
|
|
}; |