905 lines
30 KiB
JavaScript
905 lines
30 KiB
JavaScript
// ========================================
|
||
// Gallery 페이지 전용 JavaScript
|
||
// ========================================
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initGallery();
|
||
initLightbox();
|
||
initGalleryAnimations();
|
||
initCustom360Viewers();
|
||
});
|
||
|
||
// 갤러리 초기화
|
||
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(); // 오른쪽으로 스와이프 = 이전 이미지
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// 간단한 360도 파노라마 뷰어 - 좌우 스크롤 방식
|
||
// ========================================
|
||
|
||
class Easy360Viewer {
|
||
constructor(container, imageSrc, title) {
|
||
this.container = container;
|
||
this.imageSrc = imageSrc;
|
||
this.title = title;
|
||
|
||
// 뷰어 상태
|
||
this.currentX = 0; // 현재 X 위치
|
||
this.maxX = 0; // 최대 스크롤 가능 X
|
||
this.zoom = 1;
|
||
|
||
// 마우스/터치 상태
|
||
this.isDragging = false;
|
||
this.startX = 0;
|
||
this.lastX = 0;
|
||
this.velocity = 0;
|
||
|
||
// 자동 회전
|
||
this.isAutoRotating = false;
|
||
this.autoRotateSpeed = 1;
|
||
this.animationId = null;
|
||
|
||
// 터치 줌
|
||
this.touchCount = 0;
|
||
this.lastPinchDistance = 0;
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.createViewer();
|
||
this.bindEvents();
|
||
this.startAnimation();
|
||
}
|
||
|
||
createViewer() {
|
||
// 컨테이너 스타일 - 서버 호환성 개선
|
||
this.container.style.cssText = `
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
background: #000;
|
||
cursor: grab;
|
||
user-select: none;
|
||
-webkit-user-select: none;
|
||
-moz-user-select: none;
|
||
-ms-user-select: none;
|
||
touch-action: none;
|
||
`;
|
||
|
||
// 360도 이미지 래퍼 - 성능 최적화
|
||
this.imageWrapper = document.createElement('div');
|
||
this.imageWrapper.style.cssText = `
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
height: 100%;
|
||
will-change: transform;
|
||
display: flex;
|
||
align-items: center;
|
||
transform-origin: left center;
|
||
backface-visibility: hidden;
|
||
`;
|
||
|
||
// 로딩 표시
|
||
this.showLoading();
|
||
|
||
// 메인 360도 이미지 - 고해상도 설정
|
||
this.image = document.createElement('img');
|
||
this.image.src = this.imageSrc;
|
||
this.image.style.cssText = `
|
||
height: 100%;
|
||
width: auto;
|
||
object-fit: cover;
|
||
user-select: none;
|
||
pointer-events: none;
|
||
display: block;
|
||
image-rendering: -webkit-optimize-contrast;
|
||
image-rendering: crisp-edges;
|
||
filter: contrast(1.05) saturate(1.1);
|
||
`;
|
||
this.image.draggable = false;
|
||
|
||
// 이미지 로드 완료 후 - 안정성 개선
|
||
this.image.onload = () => {
|
||
// 이미지가 완전히 로드될 때까지 대기
|
||
setTimeout(() => {
|
||
this.hideLoading();
|
||
this.setupImageSize();
|
||
this.createDuplicateImages();
|
||
this.updateTransform();
|
||
}, 100);
|
||
};
|
||
|
||
this.image.onerror = () => {
|
||
this.hideLoading();
|
||
this.showError();
|
||
};
|
||
|
||
this.imageWrapper.appendChild(this.image);
|
||
this.container.appendChild(this.imageWrapper);
|
||
}
|
||
|
||
setupImageSize() {
|
||
// 이미지 자연 어숙비를 유지하면서 컨테이너 높이에 맞춤
|
||
const containerHeight = this.container.clientHeight;
|
||
const containerWidth = this.container.clientWidth;
|
||
const imageAspectRatio = this.image.naturalWidth / this.image.naturalHeight;
|
||
|
||
// 360도 이미지는 매우 가로가 기므로 컨테이너보다 훨씬 커야 함
|
||
let imageWidth = containerHeight * imageAspectRatio;
|
||
|
||
// 컨테이너보다 작으면 컨테이너 너비에 맞춤
|
||
if (imageWidth < containerWidth * 2) {
|
||
imageWidth = containerWidth * 3; // 360도를 위해 충분히 큼
|
||
}
|
||
|
||
// 이미지 크기 설정 - 브라우저 호환성 개선
|
||
this.image.style.width = Math.round(imageWidth) + 'px';
|
||
this.image.style.height = Math.round(containerHeight) + 'px';
|
||
this.imageWrapper.style.width = Math.round(imageWidth) + 'px';
|
||
this.imageWrapper.style.height = Math.round(containerHeight) + 'px';
|
||
|
||
// 단일 이미지 너비 저장
|
||
this.singleImageWidth = imageWidth;
|
||
}
|
||
|
||
createDuplicateImages() {
|
||
// 무한 스크롤을 위해 이미지 복사본 생성
|
||
const imageClone1 = this.image.cloneNode();
|
||
const imageClone2 = this.image.cloneNode();
|
||
|
||
// 동일한 스타일 적용 - 안정성 개선
|
||
imageClone1.style.cssText = this.image.style.cssText;
|
||
imageClone2.style.cssText = this.image.style.cssText;
|
||
imageClone1.src = this.image.src;
|
||
imageClone2.src = this.image.src;
|
||
imageClone1.draggable = false;
|
||
imageClone2.draggable = false;
|
||
|
||
this.imageWrapper.appendChild(imageClone1);
|
||
this.imageWrapper.appendChild(imageClone2);
|
||
|
||
// 래퍼 전체 너비를 3배로 설정
|
||
this.imageWrapper.style.width = (this.singleImageWidth * 3) + 'px';
|
||
|
||
// 시작 위치를 중앙 이미지로 (두 번째 이미지)
|
||
this.currentX = this.singleImageWidth;
|
||
this.updateTransform();
|
||
}
|
||
|
||
showLoading() {
|
||
const loader = document.createElement('div');
|
||
loader.className = 'panorama-loader';
|
||
loader.innerHTML = `
|
||
<div class="spinner"></div>
|
||
<p>360° 이미지 로딩중...</p>
|
||
`;
|
||
loader.style.cssText = `
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
text-align: center;
|
||
color: #fff;
|
||
z-index: 10;
|
||
pointer-events: none;
|
||
`;
|
||
|
||
this.container.appendChild(loader);
|
||
this.loadingElement = loader;
|
||
}
|
||
|
||
hideLoading() {
|
||
if (this.loadingElement) {
|
||
this.loadingElement.remove();
|
||
this.loadingElement = null;
|
||
}
|
||
}
|
||
|
||
showError() {
|
||
const errorElement = document.createElement('div');
|
||
errorElement.innerHTML = `
|
||
<div style="text-align: center; color: #ff6b6b; padding: 20px;">
|
||
<div style="font-size: 48px; margin-bottom: 10px;">😞</div>
|
||
<div style="font-size: 18px; margin-bottom: 10px;">이미지를 불러올 수 없습니다</div>
|
||
<div style="font-size: 14px; opacity: 0.8;">이미지 파일을 확인해주세요</div>
|
||
</div>
|
||
`;
|
||
errorElement.style.cssText = `
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 10;
|
||
`;
|
||
|
||
this.container.appendChild(errorElement);
|
||
}
|
||
|
||
bindEvents() {
|
||
// 마우스 이벤트
|
||
this.container.addEventListener('mousedown', this.handleStart.bind(this));
|
||
this.container.addEventListener('mousemove', this.handleMove.bind(this));
|
||
this.container.addEventListener('mouseup', this.handleEnd.bind(this));
|
||
this.container.addEventListener('mouseleave', this.handleEnd.bind(this));
|
||
|
||
// 터치 이벤트
|
||
this.container.addEventListener('touchstart', this.handleStart.bind(this), { passive: false });
|
||
this.container.addEventListener('touchmove', this.handleMove.bind(this), { passive: false });
|
||
this.container.addEventListener('touchend', this.handleEnd.bind(this));
|
||
|
||
// 휠 이벤트 (좌우 스크롤)
|
||
this.container.addEventListener('wheel', this.handleWheel.bind(this), { passive: false });
|
||
|
||
// 컨텍스트 메뉴 방지
|
||
this.container.addEventListener('contextmenu', e => e.preventDefault());
|
||
|
||
// 리사이즈
|
||
window.addEventListener('resize', this.handleResize.bind(this));
|
||
}
|
||
|
||
handleStart(e) {
|
||
e.preventDefault();
|
||
this.isDragging = true;
|
||
this.velocity = 0;
|
||
this.container.style.cursor = 'grabbing';
|
||
this.stopAutoRotate();
|
||
|
||
if (e.touches) {
|
||
this.touchCount = e.touches.length;
|
||
|
||
if (this.touchCount === 1) {
|
||
this.startX = e.touches[0].clientX;
|
||
} else if (this.touchCount === 2) {
|
||
// 핀치 줌 초기화
|
||
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
||
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
||
this.lastPinchDistance = Math.sqrt(dx * dx + dy * dy);
|
||
return; // 핀치일 때는 드래그 비활성화
|
||
}
|
||
} else {
|
||
this.startX = e.clientX;
|
||
}
|
||
|
||
this.lastX = this.startX;
|
||
}
|
||
|
||
handleMove(e) {
|
||
if (!this.isDragging) return;
|
||
e.preventDefault();
|
||
|
||
let currentX;
|
||
|
||
if (e.touches) {
|
||
if (this.touchCount === 1 && e.touches.length === 1) {
|
||
currentX = e.touches[0].clientX;
|
||
this.updatePosition(currentX);
|
||
|
||
} else if (this.touchCount === 2 && e.touches.length === 2) {
|
||
// 핀치 줌 처리
|
||
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
||
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||
|
||
if (this.lastPinchDistance > 0) {
|
||
const scale = distance / this.lastPinchDistance;
|
||
this.zoom = Math.max(0.8, Math.min(2.5, this.zoom * scale));
|
||
this.updateTransform();
|
||
}
|
||
|
||
this.lastPinchDistance = distance;
|
||
return;
|
||
}
|
||
} else {
|
||
currentX = e.clientX;
|
||
this.updatePosition(currentX);
|
||
}
|
||
}
|
||
|
||
updatePosition(currentX) {
|
||
const deltaX = currentX - this.lastX;
|
||
|
||
// 좌우 이동 (드래그 방향과 반대)
|
||
this.currentX -= deltaX;
|
||
|
||
// 속도 계산 (관성용)
|
||
this.velocity = -deltaX * 0.1;
|
||
|
||
this.updateTransform();
|
||
this.lastX = currentX;
|
||
}
|
||
|
||
handleEnd() {
|
||
this.isDragging = false;
|
||
this.touchCount = 0;
|
||
this.lastPinchDistance = 0;
|
||
this.container.style.cursor = 'grab';
|
||
}
|
||
|
||
handleWheel(e) {
|
||
e.preventDefault();
|
||
|
||
// 휠로 좌우 스크롤
|
||
const scrollSpeed = 50;
|
||
this.currentX += e.deltaY > 0 ? scrollSpeed : -scrollSpeed;
|
||
|
||
this.updateTransform();
|
||
}
|
||
|
||
handleResize() {
|
||
if (this.image && this.image.complete) {
|
||
// 리사이즈 시 비율 유지
|
||
const oldRatio = this.currentX / this.singleImageWidth;
|
||
this.setupImageSize();
|
||
this.createDuplicateImages(); // 이미지 다시 생성
|
||
this.currentX = this.singleImageWidth * oldRatio; // 비율 유지
|
||
this.updateTransform();
|
||
}
|
||
}
|
||
|
||
updateTransform() {
|
||
if (!this.imageWrapper || !this.singleImageWidth) return;
|
||
|
||
// 무한 스크롤 처리 - 더 부드럽게
|
||
if (this.currentX <= 0) {
|
||
this.currentX = this.singleImageWidth;
|
||
} else if (this.currentX >= this.singleImageWidth * 2) {
|
||
this.currentX = this.singleImageWidth;
|
||
}
|
||
|
||
// 부드러운 변환을 위해 소수점 제거
|
||
const translateX = Math.round(-this.currentX * 100) / 100;
|
||
const scale = Math.round(this.zoom * 100) / 100;
|
||
|
||
const transform = `translateX(${translateX}px) scale(${scale})`;
|
||
this.imageWrapper.style.transform = transform;
|
||
|
||
// 브라우저 호환성
|
||
this.imageWrapper.style.webkitTransform = transform;
|
||
this.imageWrapper.style.msTransform = transform;
|
||
}
|
||
|
||
startAnimation() {
|
||
const animate = () => {
|
||
// 관성 적용
|
||
if (!this.isDragging && Math.abs(this.velocity) > 0.5) {
|
||
this.currentX += this.velocity;
|
||
this.velocity *= 0.95; // 마찰력
|
||
this.updateTransform();
|
||
}
|
||
|
||
// 자동 회전
|
||
if (this.isAutoRotating && !this.isDragging) {
|
||
this.currentX += this.autoRotateSpeed;
|
||
this.updateTransform();
|
||
}
|
||
|
||
this.animationId = requestAnimationFrame(animate);
|
||
};
|
||
|
||
animate();
|
||
}
|
||
|
||
reset() {
|
||
this.currentX = this.singleImageWidth; // 중앙 이미지로
|
||
this.zoom = 1;
|
||
this.velocity = 0;
|
||
this.stopAutoRotate();
|
||
this.updateTransform();
|
||
}
|
||
|
||
startAutoRotate() {
|
||
this.isAutoRotating = true;
|
||
}
|
||
|
||
stopAutoRotate() {
|
||
this.isAutoRotating = false;
|
||
}
|
||
|
||
zoomIn() {
|
||
this.zoom = Math.min(2.5, this.zoom * 1.2);
|
||
this.updateTransform();
|
||
}
|
||
|
||
zoomOut() {
|
||
this.zoom = Math.max(0.8, this.zoom * 0.8);
|
||
this.updateTransform();
|
||
}
|
||
|
||
destroy() {
|
||
this.stopAutoRotate();
|
||
|
||
if (this.animationId) {
|
||
cancelAnimationFrame(this.animationId);
|
||
}
|
||
|
||
window.removeEventListener('resize', this.handleResize.bind(this));
|
||
|
||
if (this.container) {
|
||
this.container.innerHTML = '';
|
||
}
|
||
}
|
||
}
|
||
|
||
let current360Viewer = null;
|
||
|
||
function initCustom360Viewers() {
|
||
createPanoramaModal();
|
||
initPanoramaPreviews();
|
||
}
|
||
|
||
function initPanoramaPreviews() {
|
||
const clickableElements = document.querySelectorAll('.panorama-clickable');
|
||
|
||
clickableElements.forEach(element => {
|
||
const imageSrc = element.dataset.image;
|
||
const title = element.dataset.title;
|
||
|
||
element.addEventListener('click', () => {
|
||
openPanoramaModal(imageSrc, title);
|
||
});
|
||
|
||
// 호버 효과
|
||
element.addEventListener('mouseenter', () => {
|
||
element.style.transform = 'scale(1.02)';
|
||
});
|
||
|
||
element.addEventListener('mouseleave', () => {
|
||
element.style.transform = 'scale(1)';
|
||
});
|
||
|
||
// 도움말 자동 사라짐
|
||
setTimeout(() => {
|
||
const helpText = element.querySelector('.panorama-help');
|
||
if (helpText) {
|
||
helpText.style.opacity = '0';
|
||
setTimeout(() => helpText.remove(), 500);
|
||
}
|
||
}, 4000);
|
||
});
|
||
}
|
||
|
||
function createPanoramaModal() {
|
||
const modalHTML = `
|
||
<div id="panorama-modal" class="panorama-modal">
|
||
<div class="panorama-modal-content">
|
||
<div id="panorama-viewer-container" class="panorama-viewer-container">
|
||
<!-- 360도 뷰어가 여기에 들어갑니다 -->
|
||
</div>
|
||
|
||
<div class="panorama-modal-controls">
|
||
<div id="panorama-modal-title" class="panorama-modal-title"></div>
|
||
<div class="panorama-control-buttons">
|
||
<button class="panorama-btn" id="panorama-reset-btn">
|
||
<i class="fas fa-home"></i>
|
||
<span>초기화</span>
|
||
</button>
|
||
<button class="panorama-btn" id="panorama-auto-btn">
|
||
<i class="fas fa-sync-alt"></i>
|
||
<span>자동회전</span>
|
||
</button>
|
||
<button class="panorama-btn" id="panorama-zoom-in-btn">
|
||
<i class="fas fa-search-plus"></i>
|
||
<span>확대</span>
|
||
</button>
|
||
<button class="panorama-btn" id="panorama-zoom-out-btn">
|
||
<i class="fas fa-search-minus"></i>
|
||
<span>축소</span>
|
||
</button>
|
||
<button class="panorama-btn" id="panorama-help-btn">
|
||
<i class="fas fa-question-circle"></i>
|
||
<span>도움말</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="panorama-modal-close" id="panorama-close-btn">
|
||
<div class="close-icon"></div>
|
||
</button>
|
||
|
||
<div class="panorama-help-panel" id="panorama-help-panel">
|
||
<h3>360° 조작 가이드</h3>
|
||
<div class="help-content">
|
||
<div class="help-item">
|
||
<i class="fas fa-mouse"></i>
|
||
<span>마우스 드래그로 화면을 회전시킬 수 있습니다</span>
|
||
</div>
|
||
<div class="help-item">
|
||
<i class="fas fa-hand-pointer"></i>
|
||
<span>터치 스크린에서는 손가락으로 드래그하세요</span>
|
||
</div>
|
||
<div class="help-item">
|
||
<i class="fas fa-search"></i>
|
||
<span>마우스 휠이나 핀치로 확대/축소할 수 있습니다</span>
|
||
</div>
|
||
<div class="help-item">
|
||
<i class="fas fa-sync-alt"></i>
|
||
<span>자동회전 버튼으로 자동으로 둘러볼 수 있습니다</span>
|
||
</div>
|
||
<div class="help-item">
|
||
<i class="fas fa-keyboard"></i>
|
||
<span>ESC 키를 눌러 닫을 수 있습니다</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||
setupPanoramaModalEvents();
|
||
}
|
||
|
||
function setupPanoramaModalEvents() {
|
||
const modal = document.getElementById('panorama-modal');
|
||
const closeBtn = document.getElementById('panorama-close-btn');
|
||
const resetBtn = document.getElementById('panorama-reset-btn');
|
||
const autoBtn = document.getElementById('panorama-auto-btn');
|
||
const zoomInBtn = document.getElementById('panorama-zoom-in-btn');
|
||
const zoomOutBtn = document.getElementById('panorama-zoom-out-btn');
|
||
const helpBtn = document.getElementById('panorama-help-btn');
|
||
const helpPanel = document.getElementById('panorama-help-panel');
|
||
|
||
let isAutoRotating = false;
|
||
let isHelpVisible = false;
|
||
|
||
// 모달 닫기
|
||
closeBtn.addEventListener('click', closePanoramaModal);
|
||
modal.addEventListener('click', (e) => {
|
||
if (e.target === modal) closePanoramaModal();
|
||
});
|
||
|
||
// ESC 키로 닫기
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape' && modal.classList.contains('active')) {
|
||
closePanoramaModal();
|
||
}
|
||
});
|
||
|
||
// 초기화 버튼
|
||
resetBtn.addEventListener('click', () => {
|
||
if (current360Viewer) {
|
||
current360Viewer.reset();
|
||
}
|
||
});
|
||
|
||
// 자동회전 버튼
|
||
autoBtn.addEventListener('click', () => {
|
||
if (current360Viewer) {
|
||
if (isAutoRotating) {
|
||
current360Viewer.stopAutoRotate();
|
||
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i><span>자동회전</span>';
|
||
autoBtn.classList.remove('active');
|
||
isAutoRotating = false;
|
||
} else {
|
||
current360Viewer.startAutoRotate();
|
||
autoBtn.innerHTML = '<i class="fas fa-pause"></i><span>정지</span>';
|
||
autoBtn.classList.add('active');
|
||
isAutoRotating = true;
|
||
}
|
||
}
|
||
});
|
||
|
||
// 확대 버튼
|
||
zoomInBtn.addEventListener('click', () => {
|
||
if (current360Viewer) {
|
||
current360Viewer.zoomIn();
|
||
}
|
||
});
|
||
|
||
// 축소 버튼
|
||
zoomOutBtn.addEventListener('click', () => {
|
||
if (current360Viewer) {
|
||
current360Viewer.zoomOut();
|
||
}
|
||
});
|
||
|
||
// 도움말 버튼
|
||
helpBtn.addEventListener('click', () => {
|
||
isHelpVisible = !isHelpVisible;
|
||
helpPanel.style.display = isHelpVisible ? 'block' : 'none';
|
||
helpBtn.classList.toggle('active', isHelpVisible);
|
||
});
|
||
|
||
// 도움말 패널 클릭시 닫기
|
||
helpPanel.addEventListener('click', (e) => {
|
||
if (e.target === helpPanel) {
|
||
isHelpVisible = false;
|
||
helpPanel.style.display = 'none';
|
||
helpBtn.classList.remove('active');
|
||
}
|
||
});
|
||
}
|
||
|
||
function openPanoramaModal(imageSrc, title) {
|
||
const modal = document.getElementById('panorama-modal');
|
||
const modalTitle = document.getElementById('panorama-modal-title');
|
||
const viewerContainer = document.getElementById('panorama-viewer-container');
|
||
|
||
modalTitle.textContent = title;
|
||
modal.classList.add('active');
|
||
document.body.style.overflow = 'hidden';
|
||
|
||
// 간단한 360도 좌우 스크롤 뷰어 생성
|
||
current360Viewer = new Easy360Viewer(viewerContainer, imageSrc, title);
|
||
}
|
||
|
||
function closePanoramaModal() {
|
||
const modal = document.getElementById('panorama-modal');
|
||
const autoBtn = document.getElementById('panorama-auto-btn');
|
||
const helpBtn = document.getElementById('panorama-help-btn');
|
||
const helpPanel = document.getElementById('panorama-help-panel');
|
||
|
||
// WebGL 뷰어 정리
|
||
if (current360Viewer) {
|
||
current360Viewer.destroy();
|
||
current360Viewer = null;
|
||
}
|
||
|
||
// UI 초기화
|
||
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i><span>자동회전</span>';
|
||
autoBtn.classList.remove('active');
|
||
helpBtn.classList.remove('active');
|
||
helpPanel.style.display = 'none';
|
||
|
||
modal.classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
|
||
// 컨테이너 정리
|
||
document.getElementById('panorama-viewer-container').innerHTML = '';
|
||
} |