465 lines
15 KiB
JavaScript
465 lines
15 KiB
JavaScript
// ========================================
|
||
// Gallery 페이지 전용 JavaScript
|
||
// ========================================
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initGallery();
|
||
initLightbox();
|
||
initGalleryAnimations();
|
||
initPannellumViewers();
|
||
});
|
||
|
||
// 갤러리 초기화
|
||
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 = `
|
||
<div id="lightbox" class="lightbox">
|
||
<div class="lightbox-content">
|
||
<img id="lightbox-img" class="lightbox-img" src="" alt="">
|
||
<div id="lightbox-caption" class="lightbox-caption"></div>
|
||
<button class="lightbox-close" onclick="closeLightbox()"></button>
|
||
<button class="lightbox-nav lightbox-prev" onclick="previousImage()">‹</button>
|
||
<button class="lightbox-nav lightbox-next" onclick="nextImage()">›</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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(); // 오른쪽으로 스와이프 = 이전 이미지
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// Pannellum 기반 360도 이미지 뷰어 기능
|
||
// ========================================
|
||
|
||
let currentPanoramaViewer = null;
|
||
|
||
function initPannellumViewers() {
|
||
// 모달 생성
|
||
createPannellumModal();
|
||
|
||
// 미리보기 이미지 설정
|
||
initPanoramaPreviews();
|
||
}
|
||
|
||
function initPanoramaPreviews() {
|
||
const containers = document.querySelectorAll('.panorama-clickable');
|
||
|
||
containers.forEach(container => {
|
||
const preview = container.querySelector('.panorama-preview');
|
||
const imageSrc = container.dataset.image;
|
||
const title = container.dataset.title;
|
||
|
||
// 미리보기 이미지 설정
|
||
preview.style.backgroundImage = `url('${imageSrc}')`;
|
||
|
||
// 클릭 이벤트 - 전체화면 Pannellum 뷰어 열기
|
||
container.addEventListener('click', () => {
|
||
openPannellumModal(imageSrc, title);
|
||
});
|
||
|
||
// 도움말 자동 숨김
|
||
setTimeout(() => {
|
||
const help = container.querySelector('.panorama-help');
|
||
if (help) {
|
||
help.style.opacity = '0';
|
||
setTimeout(() => help.remove(), 1000);
|
||
}
|
||
}, 4000);
|
||
});
|
||
}
|
||
|
||
function createPannellumModal() {
|
||
const modalHTML = `
|
||
<div id="pannellum-modal" class="panorama-modal">
|
||
<div class="panorama-modal-content">
|
||
<div id="pannellum-viewer" class="panorama-modal-viewer"></div>
|
||
<div class="panorama-modal-controls">
|
||
<div id="pannellum-modal-title" class="panorama-modal-title"></div>
|
||
<div class="panorama-modal-buttons">
|
||
<button class="panorama-modal-btn" id="pannellum-reset-btn">
|
||
<i class="fas fa-redo"></i> 리셋
|
||
</button>
|
||
<button class="panorama-modal-btn" id="pannellum-auto-btn">
|
||
<i class="fas fa-play"></i> 자동회전
|
||
</button>
|
||
<button class="panorama-modal-btn" id="pannellum-fullscreen-btn">
|
||
<i class="fas fa-expand"></i> 전체화면
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<button class="panorama-modal-close" id="pannellum-modal-close">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||
|
||
// 모달 이벤트 리스너 설정
|
||
setupPannellumModalListeners();
|
||
}
|
||
|
||
function setupPannellumModalListeners() {
|
||
const modal = document.getElementById('pannellum-modal');
|
||
const closeBtn = document.getElementById('pannellum-modal-close');
|
||
const resetBtn = document.getElementById('pannellum-reset-btn');
|
||
const autoBtn = document.getElementById('pannellum-auto-btn');
|
||
const fullscreenBtn = document.getElementById('pannellum-fullscreen-btn');
|
||
|
||
let isAutoRotating = false;
|
||
|
||
// 모달 닫기 이벤트
|
||
closeBtn.addEventListener('click', closePannellumModal);
|
||
|
||
// ESC 키로 모달 닫기
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape' && modal.classList.contains('active')) {
|
||
closePannellumModal();
|
||
}
|
||
});
|
||
|
||
// 리셋 버튼
|
||
resetBtn.addEventListener('click', () => {
|
||
if (currentPanoramaViewer) {
|
||
currentPanoramaViewer.setPitch(0);
|
||
currentPanoramaViewer.setYaw(0);
|
||
stopAutoRotate();
|
||
}
|
||
});
|
||
|
||
// 자동 회전 버튼
|
||
autoBtn.addEventListener('click', () => {
|
||
if (isAutoRotating) {
|
||
stopAutoRotate();
|
||
} else {
|
||
startAutoRotate();
|
||
}
|
||
});
|
||
|
||
// 전체화면 버튼
|
||
fullscreenBtn.addEventListener('click', () => {
|
||
if (currentPanoramaViewer) {
|
||
currentPanoramaViewer.toggleFullscreen();
|
||
}
|
||
});
|
||
|
||
function startAutoRotate() {
|
||
if (currentPanoramaViewer) {
|
||
currentPanoramaViewer.startAutoRotate(0.5); // 초당 0.5도 회전
|
||
isAutoRotating = true;
|
||
autoBtn.innerHTML = '<i class="fas fa-pause"></i> 정지';
|
||
autoBtn.classList.add('active');
|
||
}
|
||
}
|
||
|
||
function stopAutoRotate() {
|
||
if (currentPanoramaViewer) {
|
||
currentPanoramaViewer.stopAutoRotate();
|
||
isAutoRotating = false;
|
||
autoBtn.innerHTML = '<i class="fas fa-play"></i> 자동회전';
|
||
autoBtn.classList.remove('active');
|
||
}
|
||
}
|
||
}
|
||
|
||
function openPannellumModal(imageSrc, title) {
|
||
const modal = document.getElementById('pannellum-modal');
|
||
const modalTitle = document.getElementById('pannellum-modal-title');
|
||
|
||
modalTitle.textContent = title;
|
||
modal.classList.add('active');
|
||
document.body.style.overflow = 'hidden';
|
||
|
||
// Pannellum 뷰어 초기화
|
||
currentPanoramaViewer = pannellum.viewer('pannellum-viewer', {
|
||
type: 'equirectangular',
|
||
panorama: imageSrc,
|
||
autoLoad: true,
|
||
autoRotate: 0,
|
||
compass: true,
|
||
northOffset: 0,
|
||
preview: imageSrc,
|
||
hfov: 100,
|
||
minHfov: 50,
|
||
maxHfov: 120,
|
||
pitch: 0,
|
||
yaw: 0,
|
||
mouseZoom: true,
|
||
keyboardZoom: true,
|
||
draggable: true,
|
||
disableKeyboardCtrl: false,
|
||
showControls: false,
|
||
showFullscreenCtrl: false,
|
||
showZoomCtrl: false,
|
||
hotSpotDebug: false,
|
||
backgroundColor: [0, 0, 0],
|
||
orientationOnByDefault: false
|
||
});
|
||
|
||
// 뷰어 로드 완료 이벤트
|
||
currentPanoramaViewer.on('load', function() {
|
||
console.log('Pannellum 360도 뷰어 로드 완료');
|
||
});
|
||
}
|
||
|
||
function closePannellumModal() {
|
||
const modal = document.getElementById('pannellum-modal');
|
||
const autoBtn = document.getElementById('pannellum-auto-btn');
|
||
|
||
// 자동 회전 정지
|
||
if (autoBtn.classList.contains('active')) {
|
||
autoBtn.click();
|
||
}
|
||
|
||
// 뷰어 정리
|
||
if (currentPanoramaViewer) {
|
||
currentPanoramaViewer.destroy();
|
||
currentPanoramaViewer = null;
|
||
}
|
||
|
||
modal.classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
|
||
// 뷰어 컨테이너 초기화
|
||
document.getElementById('pannellum-viewer').innerHTML = '';
|
||
} |