// ======================================== // Gallery 페이지 전용 JavaScript // ======================================== document.addEventListener('DOMContentLoaded', function() { initGallery(); initLightbox(); initGalleryAnimations(); initAFrame360Viewers(); }); // 갤러리 초기화 function initGallery() { const galleryItems = document.querySelectorAll('.gallery-item'); galleryItems.forEach((item, index) => { const img = item.querySelector('.gallery-img'); // 이미지 클릭 시 라이트박스 열기 img.addEventListener('click', () => openLightbox(index)); // 이미지 로딩 에러 처리 img.addEventListener('error', function() { this.src = 'images/placeholder.jpg'; this.alt = '이미지를 불러올 수 없습니다'; }); // 레이지 로딩 구현 if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src || img.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }); if (img.dataset.src) { imageObserver.observe(img); } } }); } // 라이트박스 기능 let currentImageIndex = 0; const galleryImages = document.querySelectorAll('.gallery-img'); function initLightbox() { // 라이트박스 HTML 생성 const lightboxHTML = ` `; document.body.insertAdjacentHTML('beforeend', lightboxHTML); // ESC 키로 라이트박스 닫기 document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeLightbox(); if (e.key === 'ArrowLeft') previousImage(); if (e.key === 'ArrowRight') nextImage(); }); // 배경 클릭으로 라이트박스 닫기 document.getElementById('lightbox').addEventListener('click', function(e) { if (e.target === this) closeLightbox(); }); } function openLightbox(index) { currentImageIndex = index; const lightbox = document.getElementById('lightbox'); const lightboxImg = document.getElementById('lightbox-img'); const lightboxCaption = document.getElementById('lightbox-caption'); const currentImg = galleryImages[index]; const caption = currentImg.closest('.gallery-item').querySelector('.gallery-caption'); lightboxImg.src = currentImg.src; lightboxImg.alt = currentImg.alt; lightboxCaption.textContent = caption ? caption.textContent : currentImg.alt; lightbox.classList.add('active'); document.body.style.overflow = 'hidden'; } function closeLightbox() { const lightbox = document.getElementById('lightbox'); lightbox.classList.remove('active'); document.body.style.overflow = ''; } function nextImage() { currentImageIndex = (currentImageIndex + 1) % galleryImages.length; openLightbox(currentImageIndex); } function previousImage() { currentImageIndex = (currentImageIndex - 1 + galleryImages.length) % galleryImages.length; openLightbox(currentImageIndex); } // 갤러리 애니메이션 function initGalleryAnimations() { const galleryItems = document.querySelectorAll('.gallery-item'); // Intersection Observer를 사용한 애니메이션 const observerOptions = { threshold: 0.1, rootMargin: '50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach((entry, index) => { if (entry.isIntersecting) { setTimeout(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; }, index * 100); observer.unobserve(entry.target); } }); }, observerOptions); galleryItems.forEach(item => { item.style.opacity = '0'; item.style.transform = 'translateY(30px)'; item.style.transition = 'all 0.6s cubic-bezier(0.4, 0, 0.2, 1)'; observer.observe(item); }); } // 갤러리 필터 기능 (향후 확장용) function initGalleryFilters() { const filterButtons = document.querySelectorAll('.filter-btn'); const galleryItems = document.querySelectorAll('.gallery-item'); filterButtons.forEach(btn => { btn.addEventListener('click', function() { // 활성 버튼 업데이트 filterButtons.forEach(b => b.classList.remove('active')); this.classList.add('active'); const filter = this.dataset.filter; galleryItems.forEach(item => { if (filter === 'all' || item.dataset.category === filter) { item.style.display = 'block'; setTimeout(() => { item.style.opacity = '1'; item.style.transform = 'scale(1)'; }, 10); } else { item.style.opacity = '0'; item.style.transform = 'scale(0.8)'; setTimeout(() => { item.style.display = 'none'; }, 300); } }); }); }); } // 이미지 프리로딩 function preloadImages() { galleryImages.forEach(img => { const imagePreload = new Image(); imagePreload.src = img.src; }); } // 갤러리 검색 기능 (향후 확장용) function initGallerySearch() { const searchInput = document.getElementById('gallery-search'); const galleryItems = document.querySelectorAll('.gallery-item'); if (searchInput) { searchInput.addEventListener('input', function() { const searchTerm = this.value.toLowerCase(); galleryItems.forEach(item => { const caption = item.querySelector('.gallery-caption'); const alt = item.querySelector('.gallery-img').alt; const text = (caption ? caption.textContent : '') + ' ' + alt; if (text.toLowerCase().includes(searchTerm)) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); }); } } // 이미지 로딩 상태 표시 function showGalleryLoading() { const loading = document.querySelector('.gallery-loading'); if (loading) { loading.style.display = 'block'; } } function hideGalleryLoading() { const loading = document.querySelector('.gallery-loading'); if (loading) { loading.style.display = 'none'; } } // 갤러리 그리드 리사이즈 최적화 let resizeTimeout; window.addEventListener('resize', function() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(function() { // 갤러리 그리드 재조정 로직 const galleryGrid = document.querySelector('.gallery-grid'); if (galleryGrid) { galleryGrid.style.opacity = '0.8'; setTimeout(() => { galleryGrid.style.opacity = '1'; }, 100); } }, 250); }); // 터치 이벤트 지원 (모바일) let touchStartX = 0; let touchEndX = 0; document.addEventListener('touchstart', function(e) { if (document.getElementById('lightbox').classList.contains('active')) { touchStartX = e.changedTouches[0].screenX; } }); document.addEventListener('touchend', function(e) { if (document.getElementById('lightbox').classList.contains('active')) { touchEndX = e.changedTouches[0].screenX; handleSwipe(); } }); function handleSwipe() { const swipeThreshold = 50; const diff = touchStartX - touchEndX; if (Math.abs(diff) > swipeThreshold) { if (diff > 0) { nextImage(); // 왼쪽으로 스와이프 = 다음 이미지 } else { previousImage(); // 오른쪽으로 스와이프 = 이전 이미지 } } } // ======================================== // A-Frame 기반 360도 VR 뷰어 - 안정적이고 강력한 VR 체험 // ======================================== let currentAFrameScene = null; let autoRotationInterval = null; let isAutoRotating = false; function initAFrame360Viewers() { // 모달 생성 createAFrameModal(); // 미리보기 설정 initAFramePreviews(); } function initAFramePreviews() { const containers = document.querySelectorAll('.panorama-clickable'); containers.forEach(container => { const imageSrc = container.dataset.image; const title = container.dataset.title; // 클릭 이벤트 container.addEventListener('click', () => { openAFrameModal(imageSrc, title); }); // 도움말 자동 숨김 setTimeout(() => { const help = container.querySelector('.panorama-help'); if (help) { help.style.opacity = '0'; setTimeout(() => help.remove(), 1000); } }, 4000); }); } function createAFrameModal() { const modalHTML = `

360° VR 조작법

🖱️ 드래그: 전방향 회전

📱 터치: 터치 후 드래그

🖱️ 휠: 줌 인/아웃

📱 핀치: 줌 인/아웃

⌨️ WASD: 이동

📱 VR모드: VR 헤드셋 지원

⌨️ ESC: 닫기

`; document.body.insertAdjacentHTML('beforeend', modalHTML); setupAFrameModalListeners(); } function setupAFrameModalListeners() { const modal = document.getElementById('aframe-modal'); const closeBtn = document.getElementById('aframe-modal-close'); const resetBtn = document.getElementById('aframe-reset-btn'); const autoBtn = document.getElementById('aframe-auto-btn'); const vrBtn = document.getElementById('aframe-vr-btn'); const helpBtn = document.getElementById('aframe-help-btn'); const helpText = document.getElementById('aframe-help-text'); let helpVisible = false; // 모달 닫기 closeBtn.addEventListener('click', closeAFrameModal); modal.addEventListener('click', (e) => { if (e.target === modal) closeAFrameModal(); }); // ESC 키 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modal.classList.contains('active')) { closeAFrameModal(); } }); // 리셋 버튼 resetBtn.addEventListener('click', () => { if (currentAFrameScene) { const camera = currentAFrameScene.querySelector('[camera]'); if (camera) { camera.setAttribute('rotation', '0 0 0'); camera.setAttribute('position', '0 0 0'); } } }); // 자동 회전 버튼 autoBtn.addEventListener('click', () => { if (isAutoRotating) { stopAutoRotation(); autoBtn.innerHTML = ' 자동회전'; autoBtn.classList.remove('active'); } else { startAutoRotation(); autoBtn.innerHTML = ' 정지'; autoBtn.classList.add('active'); } }); // VR 버튼 vrBtn.addEventListener('click', () => { if (currentAFrameScene) { const vrButton = currentAFrameScene.querySelector('[vr-mode-ui]'); if (vrButton) { // VR 모드 진입 currentAFrameScene.enterVR(); } } }); // 도움말 버튼 helpBtn.addEventListener('click', () => { helpVisible = !helpVisible; helpText.style.display = helpVisible ? 'block' : 'none'; helpBtn.classList.toggle('active', helpVisible); }); // 도움말 자동 숨김 setTimeout(() => { helpText.style.display = 'none'; }, 6000); } function openAFrameModal(imageSrc, title) { const modal = document.getElementById('aframe-modal'); const modalTitle = document.getElementById('aframe-modal-title'); const viewerContainer = document.getElementById('aframe-viewer'); modalTitle.textContent = title; modal.classList.add('active'); document.body.style.overflow = 'hidden'; // A-Frame Scene 생성 const sceneHTML = ` `; viewerContainer.innerHTML = sceneHTML; currentAFrameScene = document.getElementById('aframe-scene'); // A-Frame 로딩 완료 대기 currentAFrameScene.addEventListener('loaded', () => { console.log('A-Frame 360도 뷰어 로드 완료:', title); }); } function startAutoRotation() { if (!currentAFrameScene) return; isAutoRotating = true; const camera = currentAFrameScene.querySelector('#panorama-camera'); let currentRotationY = 0; autoRotationInterval = setInterval(() => { if (camera && isAutoRotating) { currentRotationY += 0.5; // 초당 0.5도 회전 if (currentRotationY >= 360) currentRotationY = 0; const currentRotation = camera.getAttribute('rotation'); camera.setAttribute('rotation', `${currentRotation.x} ${currentRotationY} ${currentRotation.z}`); } }, 50); // 20fps } function stopAutoRotation() { isAutoRotating = false; if (autoRotationInterval) { clearInterval(autoRotationInterval); autoRotationInterval = null; } } function closeAFrameModal() { const modal = document.getElementById('aframe-modal'); const autoBtn = document.getElementById('aframe-auto-btn'); // 자동 회전 정지 stopAutoRotation(); // 버튼 초기화 autoBtn.innerHTML = ' 자동회전'; autoBtn.classList.remove('active'); // A-Frame Scene 정리 if (currentAFrameScene) { currentAFrameScene.destroy(); currentAFrameScene = null; } modal.classList.remove('active'); document.body.style.overflow = ''; // 컨테이너 정리 document.getElementById('aframe-viewer').innerHTML = ''; }