mingle-website/js/gallery.js

465 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ========================================
// 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 = '';
}