Fix : 갤러리 업데이트

This commit is contained in:
qsxft258@gmail.com 2025-09-17 21:47:02 +09:00
parent c1a85bba85
commit e50cfa698f
2 changed files with 152 additions and 123 deletions

View File

@ -40,8 +40,8 @@
<!-- 아이콘 폰트 (이모지 대체용) --> <!-- 아이콘 폰트 (이모지 대체용) -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<!-- Photo Sphere Viewer --> <!-- A-Frame VR Framework -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/core@5/index.css"> <script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<link rel="stylesheet" href="css/common.css"> <link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/gallery.css"> <link rel="stylesheet" href="css/gallery.css">
@ -171,8 +171,6 @@
<div id="footer-placeholder"></div> <div id="footer-placeholder"></div>
<script src="js/common.js"></script> <script src="js/common.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/core@5/index.js"></script>
<script src="js/gallery.js"></script> <script src="js/gallery.js"></script>
</body> </body>
</html> </html>

View File

@ -6,7 +6,7 @@ document.addEventListener('DOMContentLoaded', function() {
initGallery(); initGallery();
initLightbox(); initLightbox();
initGalleryAnimations(); initGalleryAnimations();
initPhotoSphereViewers(); initAFrame360Viewers();
}); });
// 갤러리 초기화 // 갤러리 초기화
@ -267,20 +267,22 @@ function handleSwipe() {
} }
// ======================================== // ========================================
// Photo Sphere Viewer - 진짜 360도 VR 뷰어 // A-Frame 기반 360도 VR 뷰어 - 안정적이고 강력한 VR 체험
// ======================================== // ========================================
let currentPhotoSphereViewer = null; let currentAFrameScene = null;
let autoRotationInterval = null;
let isAutoRotating = false;
function initPhotoSphereViewers() { function initAFrame360Viewers() {
// 모달 생성 // 모달 생성
createPhotoSphereModal(); createAFrameModal();
// 미리보기 설정 // 미리보기 설정
initPhotoSpherePreviews(); initAFramePreviews();
} }
function initPhotoSpherePreviews() { function initAFramePreviews() {
const containers = document.querySelectorAll('.panorama-clickable'); const containers = document.querySelectorAll('.panorama-clickable');
containers.forEach(container => { containers.forEach(container => {
@ -289,7 +291,7 @@ function initPhotoSpherePreviews() {
// 클릭 이벤트 // 클릭 이벤트
container.addEventListener('click', () => { container.addEventListener('click', () => {
openPhotoSphereModal(imageSrc, title); openAFrameModal(imageSrc, title);
}); });
// 도움말 자동 숨김 // 도움말 자동 숨김
@ -303,39 +305,41 @@ function initPhotoSpherePreviews() {
}); });
} }
function createPhotoSphereModal() { function createAFrameModal() {
const modalHTML = ` const modalHTML = `
<div id="photosphere-modal" class="panorama-modal"> <div id="aframe-modal" class="panorama-modal">
<div class="panorama-modal-content"> <div class="panorama-modal-content">
<div id="photosphere-viewer" class="panorama-modal-viewer"></div> <div id="aframe-viewer" class="panorama-modal-viewer">
<!-- A-Frame Scene will be injected here -->
</div>
<div class="panorama-modal-controls"> <div class="panorama-modal-controls">
<div id="photosphere-modal-title" class="panorama-modal-title"></div> <div id="aframe-modal-title" class="panorama-modal-title"></div>
<div class="panorama-modal-buttons"> <div class="panorama-modal-buttons">
<button class="panorama-modal-btn" id="photosphere-reset-btn"> <button class="panorama-modal-btn" id="aframe-reset-btn">
<i class="fas fa-home"></i> <i class="fas fa-home"></i>
</button> </button>
<button class="panorama-modal-btn" id="photosphere-auto-btn"> <button class="panorama-modal-btn" id="aframe-auto-btn">
<i class="fas fa-sync-alt"></i> <i class="fas fa-sync-alt"></i>
</button> </button>
<button class="panorama-modal-btn" id="photosphere-fullscreen-btn"> <button class="panorama-modal-btn" id="aframe-vr-btn">
<i class="fas fa-expand"></i> <i class="fas fa-vr-cardboard"></i> VR
</button> </button>
<button class="panorama-modal-btn" id="photosphere-help-btn"> <button class="panorama-modal-btn" id="aframe-help-btn">
<i class="fas fa-question-circle"></i> <i class="fas fa-question-circle"></i>
</button> </button>
</div> </div>
</div> </div>
<button class="panorama-modal-close" id="photosphere-modal-close"> <button class="panorama-modal-close" id="aframe-modal-close">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
<div class="panorama-modal-help-text" id="photosphere-help-text"> <div class="panorama-modal-help-text" id="aframe-help-text">
<p><strong>360° VR 조작법</strong></p> <p><strong>360° VR 조작법</strong></p>
<p>🖱 드래그: 전방향 회전</p> <p>🖱 드래그: 전방향 회전</p>
<p>📱 터치: 터치 드래그</p> <p>📱 터치: 터치 드래그</p>
<p>🖱 : /아웃</p> <p>🖱 : /아웃</p>
<p>📱 핀치: /아웃</p> <p>📱 핀치: /아웃</p>
<p> 화살표: 방향 이동</p> <p> WASD: 이동</p>
<p> +/-: </p> <p>📱 VR모드: VR 헤드셋 지원</p>
<p> ESC: 닫기</p> <p> ESC: 닫기</p>
</div> </div>
</div> </div>
@ -343,67 +347,65 @@ function createPhotoSphereModal() {
`; `;
document.body.insertAdjacentHTML('beforeend', modalHTML); document.body.insertAdjacentHTML('beforeend', modalHTML);
setupPhotoSphereModalListeners(); setupAFrameModalListeners();
} }
function setupPhotoSphereModalListeners() { function setupAFrameModalListeners() {
const modal = document.getElementById('photosphere-modal'); const modal = document.getElementById('aframe-modal');
const closeBtn = document.getElementById('photosphere-modal-close'); const closeBtn = document.getElementById('aframe-modal-close');
const resetBtn = document.getElementById('photosphere-reset-btn'); const resetBtn = document.getElementById('aframe-reset-btn');
const autoBtn = document.getElementById('photosphere-auto-btn'); const autoBtn = document.getElementById('aframe-auto-btn');
const fullscreenBtn = document.getElementById('photosphere-fullscreen-btn'); const vrBtn = document.getElementById('aframe-vr-btn');
const helpBtn = document.getElementById('photosphere-help-btn'); const helpBtn = document.getElementById('aframe-help-btn');
const helpText = document.getElementById('photosphere-help-text'); const helpText = document.getElementById('aframe-help-text');
let isAutoRotating = false;
let helpVisible = false; let helpVisible = false;
// 모달 닫기 // 모달 닫기
closeBtn.addEventListener('click', closePhotoSphereModal); closeBtn.addEventListener('click', closeAFrameModal);
modal.addEventListener('click', (e) => { modal.addEventListener('click', (e) => {
if (e.target === modal) closePhotoSphereModal(); if (e.target === modal) closeAFrameModal();
}); });
// ESC 키 // ESC 키
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.classList.contains('active')) { if (e.key === 'Escape' && modal.classList.contains('active')) {
closePhotoSphereModal(); closeAFrameModal();
} }
}); });
// 리셋 버튼 // 리셋 버튼
resetBtn.addEventListener('click', () => { resetBtn.addEventListener('click', () => {
if (currentPhotoSphereViewer) { if (currentAFrameScene) {
currentPhotoSphereViewer.animate({ const camera = currentAFrameScene.querySelector('[camera]');
yaw: 0, if (camera) {
pitch: 0, camera.setAttribute('rotation', '0 0 0');
zoom: 50, camera.setAttribute('position', '0 0 0');
speed: '2rpm' }
});
} }
}); });
// 자동 회전 버튼 // 자동 회전 버튼
autoBtn.addEventListener('click', () => { autoBtn.addEventListener('click', () => {
if (currentPhotoSphereViewer) { if (isAutoRotating) {
if (isAutoRotating) { stopAutoRotation();
currentPhotoSphereViewer.stopAutorotate(); autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전';
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전'; autoBtn.classList.remove('active');
autoBtn.classList.remove('active'); } else {
isAutoRotating = false; startAutoRotation();
} else { autoBtn.innerHTML = '<i class="fas fa-pause"></i> 정지';
currentPhotoSphereViewer.startAutorotate(); autoBtn.classList.add('active');
autoBtn.innerHTML = '<i class="fas fa-pause"></i> 정지';
autoBtn.classList.add('active');
isAutoRotating = true;
}
} }
}); });
// 전체화면 버튼 // VR 버튼
fullscreenBtn.addEventListener('click', () => { vrBtn.addEventListener('click', () => {
if (currentPhotoSphereViewer) { if (currentAFrameScene) {
currentPhotoSphereViewer.enterFullscreen(); const vrButton = currentAFrameScene.querySelector('[vr-mode-ui]');
if (vrButton) {
// VR 모드 진입
currentAFrameScene.enterVR();
}
} }
}); });
@ -420,84 +422,113 @@ function setupPhotoSphereModalListeners() {
}, 6000); }, 6000);
} }
function openPhotoSphereModal(imageSrc, title) { function openAFrameModal(imageSrc, title) {
const modal = document.getElementById('photosphere-modal'); const modal = document.getElementById('aframe-modal');
const modalTitle = document.getElementById('photosphere-modal-title'); const modalTitle = document.getElementById('aframe-modal-title');
const viewerContainer = document.getElementById('photosphere-viewer'); const viewerContainer = document.getElementById('aframe-viewer');
modalTitle.textContent = title; modalTitle.textContent = title;
modal.classList.add('active'); modal.classList.add('active');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
// Photo Sphere Viewer 생성 // A-Frame Scene 생성
try { const sceneHTML = `
currentPhotoSphereViewer = new PhotoSphereViewer.Viewer({ <a-scene
container: viewerContainer, id="aframe-scene"
panorama: imageSrc, embedded
caption: title, style="height: 100%; width: 100%;"
loadingImg: imageSrc, // 로딩 중 미리보기 vr-mode-ui="enabled: true"
touchmoveTwoFingers: true, background="color: #000000">
mousewheelCtrlKey: false,
defaultYaw: 0,
defaultPitch: 0,
defaultZoomLvl: 50,
minFov: 30,
maxFov: 90,
autorotateDelay: null,
autorotateIdle: false,
autorotateSpeed: '2rpm',
fisheye: false,
navbar: [
'zoom',
'move',
'fullscreen',
{
title: '자동회전',
content: '🔄',
onClick: () => {
const autoBtn = document.getElementById('photosphere-auto-btn');
autoBtn.click();
}
}
]
});
// 이벤트 리스너 <!-- 360 이미지 스카이박스 -->
currentPhotoSphereViewer.addEventListener('ready', () => { <a-sky
console.log('Photo Sphere Viewer 준비 완료'); id="panorama-sky"
}); src="${imageSrc}"
rotation="0 -90 0">
</a-sky>
currentPhotoSphereViewer.addEventListener('autorotate', () => { <!-- 카메라 (사용자 시점) -->
console.log('자동 회전 시작'); <a-camera
}); id="panorama-camera"
look-controls="enabled: true; touchEnabled: true"
wasd-controls="enabled: false"
position="0 0 0"
rotation="0 0 0"
fov="80"
near="0.1"
far="1000">
currentPhotoSphereViewer.addEventListener('autorotate-stop', () => { <!-- 커서 (VR 모드용) -->
console.log('자동 회전 정지'); <a-cursor
}); animation__click="property: scale; startEvents: click; from: 0.1 0.1 0.1; to: 1 1 1; dur: 150"
animation__fusing="property: scale; startEvents: fusing; from: 1 1 1; to: 0.1 0.1 0.1; dur: 1500"
raycaster="objects: .clickable"
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
material="color: white; shader: flat">
</a-cursor>
</a-camera>
} catch (error) { <!-- 조명 -->
console.error('Photo Sphere Viewer 초기화 실패:', error); <a-light type="ambient" color="#404040" intensity="0.4"></a-light>
alert('360도 뷰어를 로드할 수 없습니다. 페이지를 새로고침해주세요.'); <a-light type="directional" position="1 1 1" color="#ffffff" intensity="0.6"></a-light>
</a-scene>
`;
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 closePhotoSphereModal() { function closeAFrameModal() {
const modal = document.getElementById('photosphere-modal'); const modal = document.getElementById('aframe-modal');
const autoBtn = document.getElementById('photosphere-auto-btn'); const autoBtn = document.getElementById('aframe-auto-btn');
// 뷰어 정리 // 자동 회전 정지
if (currentPhotoSphereViewer) { stopAutoRotation();
currentPhotoSphereViewer.destroy();
currentPhotoSphereViewer = null;
}
// 버튼 초기화 // 버튼 초기화
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전'; autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전';
autoBtn.classList.remove('active'); autoBtn.classList.remove('active');
// A-Frame Scene 정리
if (currentAFrameScene) {
currentAFrameScene.destroy();
currentAFrameScene = null;
}
modal.classList.remove('active'); modal.classList.remove('active');
document.body.style.overflow = ''; document.body.style.overflow = '';
// 컨테이너 정리 // 컨테이너 정리
document.getElementById('photosphere-viewer').innerHTML = ''; document.getElementById('aframe-viewer').innerHTML = '';
} }