Fix : 360 이미지 업데이트

This commit is contained in:
qsxft258@gmail.com 2025-09-17 21:43:58 +09:00
parent 79fd7f22f6
commit c1a85bba85
3 changed files with 254 additions and 125 deletions

View File

@ -302,13 +302,39 @@
overflow: hidden; overflow: hidden;
} }
.panorama-preview { .panorama-preview-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-size: cover; object-fit: cover;
background-position: center; transition: var(--transition);
background-repeat: no-repeat; }
position: relative;
.panorama-play-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80px;
height: 80px;
background: rgba(255, 136, 0, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-2xl);
color: var(--text-white);
transition: var(--transition);
box-shadow: 0 8px 25px rgba(255, 136, 0, 0.3);
backdrop-filter: blur(10px);
}
.panorama-play-button i {
margin-left: 3px; /* 플레이 아이콘 중앙 정렬 보정 */
}
.panorama-clickable:hover .panorama-play-button {
transform: translate(-50%, -50%) scale(1.1);
box-shadow: 0 12px 35px rgba(255, 136, 0, 0.4);
} }
.panorama-controls { .panorama-controls {
@ -386,7 +412,7 @@
20%, 80% { opacity: 1; } 20%, 80% { opacity: 1; }
} }
/* Pannellum 360도 이미지 전체화면 모달 */ /* 개선된 360도 이미지 전체화면 모달 */
.panorama-modal { .panorama-modal {
display: none; display: none;
position: fixed; position: fixed;
@ -396,21 +422,48 @@
height: 100vh; height: 100vh;
background: rgba(0, 0, 0, 0.95); background: rgba(0, 0, 0, 0.95);
z-index: 2000; z-index: 2000;
cursor: grab;
} }
.panorama-modal.active { .panorama-modal.active {
display: block; display: flex;
align-items: center;
justify-content: center;
}
.panorama-modal:active {
cursor: grabbing;
} }
.panorama-modal-content { .panorama-modal-content {
position: relative; position: relative;
width: 100vw; width: 90vw;
height: 100vh; height: 70vh;
max-width: 1200px;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
} }
.panorama-modal-viewer { .panorama-modal-viewer {
position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
border-radius: var(--border-radius);
background: #000;
}
.panorama-modal-image {
position: absolute;
top: 0;
left: 0;
height: 100%;
object-fit: cover;
transition: transform 0.1s ease-out;
will-change: transform;
user-select: none;
pointer-events: none;
} }
.panorama-modal-controls { .panorama-modal-controls {
@ -490,6 +543,31 @@
font-weight: bold; font-weight: bold;
} }
.panorama-modal-help-text {
position: absolute;
top: var(--spacing-xl);
right: var(--spacing-xl);
background: rgba(0, 0, 0, 0.9);
color: var(--text-white);
padding: var(--spacing-lg);
border-radius: var(--border-radius);
font-size: var(--font-sm);
max-width: 250px;
z-index: 10;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
display: none;
}
.panorama-modal-help-text p {
margin: 0 0 var(--spacing-xs) 0;
}
.panorama-modal-help-text strong {
color: var(--primary-color);
font-size: var(--font-base);
}
.panorama-modal-help { .panorama-modal-help {
position: absolute; position: absolute;
top: var(--spacing-lg); top: var(--spacing-lg);

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">
<!-- Pannellum 360도 뷰어 --> <!-- Photo Sphere Viewer -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/core@5/index.css">
<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">
@ -132,9 +132,15 @@
<div class="panorama-container panorama-clickable" <div class="panorama-container panorama-clickable"
data-image="Studio_Image/커튼 걷은 360 이미지.webp" data-image="Studio_Image/커튼 걷은 360 이미지.webp"
data-title="스튜디오 전경 (커튼 열림)"> data-title="스튜디오 전경 (커튼 열림)">
<div id="panorama-preview-1" class="panorama-preview"></div> <img src="Studio_Image/커튼 걷은 360 이미지.webp"
<div class="panorama-help">클릭하여 360도로 보기</div> alt="밍글 스튜디오 360도 전경 - 커튼을 걷은 모습"
class="panorama-preview-image"
loading="lazy">
<div class="panorama-help">클릭하여 360° VR로 체험하기</div>
<div class="panorama-indicator">360° VR</div> <div class="panorama-indicator">360° VR</div>
<div class="panorama-play-button">
<i class="fas fa-play"></i>
</div>
</div> </div>
<div class="panorama-controls"> <div class="panorama-controls">
<div class="panorama-title">스튜디오 전경 (커튼 열림)</div> <div class="panorama-title">스튜디오 전경 (커튼 열림)</div>
@ -145,9 +151,15 @@
<div class="panorama-container panorama-clickable" <div class="panorama-container panorama-clickable"
data-image="Studio_Image/커튼친 360 이미지.webp" data-image="Studio_Image/커튼친 360 이미지.webp"
data-title="스튜디오 전경 (커튼 닫힘)"> data-title="스튜디오 전경 (커튼 닫힘)">
<div id="panorama-preview-2" class="panorama-preview"></div> <img src="Studio_Image/커튼친 360 이미지.webp"
<div class="panorama-help">클릭하여 360도로 보기</div> alt="밍글 스튜디오 360도 전경 - 커튼을 친 모습"
class="panorama-preview-image"
loading="lazy">
<div class="panorama-help">클릭하여 360° VR로 체험하기</div>
<div class="panorama-indicator">360° VR</div> <div class="panorama-indicator">360° VR</div>
<div class="panorama-play-button">
<i class="fas fa-play"></i>
</div>
</div> </div>
<div class="panorama-controls"> <div class="panorama-controls">
<div class="panorama-title">스튜디오 전경 (커튼 닫힘)</div> <div class="panorama-title">스튜디오 전경 (커튼 닫힘)</div>
@ -159,7 +171,8 @@
<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/pannellum@2.5.6/build/pannellum.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();
initPannellumViewers(); initPhotoSphereViewers();
}); });
// 갤러리 초기화 // 갤러리 초기화
@ -267,33 +267,29 @@ function handleSwipe() {
} }
// ======================================== // ========================================
// Pannellum 기반 360도 이미지 뷰어 기능 // Photo Sphere Viewer - 진짜 360도 VR 뷰어
// ======================================== // ========================================
let currentPanoramaViewer = null; let currentPhotoSphereViewer = null;
function initPannellumViewers() { function initPhotoSphereViewers() {
// 모달 생성 // 모달 생성
createPannellumModal(); createPhotoSphereModal();
// 미리보기 이미지 설정 // 미리보기 설정
initPanoramaPreviews(); initPhotoSpherePreviews();
} }
function initPanoramaPreviews() { function initPhotoSpherePreviews() {
const containers = document.querySelectorAll('.panorama-clickable'); const containers = document.querySelectorAll('.panorama-clickable');
containers.forEach(container => { containers.forEach(container => {
const preview = container.querySelector('.panorama-preview');
const imageSrc = container.dataset.image; const imageSrc = container.dataset.image;
const title = container.dataset.title; const title = container.dataset.title;
// 미리보기 이미지 설정 // 클릭 이벤트
preview.style.backgroundImage = `url('${imageSrc}')`;
// 클릭 이벤트 - 전체화면 Pannellum 뷰어 열기
container.addEventListener('click', () => { container.addEventListener('click', () => {
openPannellumModal(imageSrc, title); openPhotoSphereModal(imageSrc, title);
}); });
// 도움말 자동 숨김 // 도움말 자동 숨김
@ -307,159 +303,201 @@ function initPanoramaPreviews() {
}); });
} }
function createPannellumModal() { function createPhotoSphereModal() {
const modalHTML = ` const modalHTML = `
<div id="pannellum-modal" class="panorama-modal"> <div id="photosphere-modal" class="panorama-modal">
<div class="panorama-modal-content"> <div class="panorama-modal-content">
<div id="pannellum-viewer" class="panorama-modal-viewer"></div> <div id="photosphere-viewer" class="panorama-modal-viewer"></div>
<div class="panorama-modal-controls"> <div class="panorama-modal-controls">
<div id="pannellum-modal-title" class="panorama-modal-title"></div> <div id="photosphere-modal-title" class="panorama-modal-title"></div>
<div class="panorama-modal-buttons"> <div class="panorama-modal-buttons">
<button class="panorama-modal-btn" id="pannellum-reset-btn"> <button class="panorama-modal-btn" id="photosphere-reset-btn">
<i class="fas fa-redo"></i> <i class="fas fa-home"></i>
</button> </button>
<button class="panorama-modal-btn" id="pannellum-auto-btn"> <button class="panorama-modal-btn" id="photosphere-auto-btn">
<i class="fas fa-play"></i> <i class="fas fa-sync-alt"></i>
</button> </button>
<button class="panorama-modal-btn" id="pannellum-fullscreen-btn"> <button class="panorama-modal-btn" id="photosphere-fullscreen-btn">
<i class="fas fa-expand"></i> <i class="fas fa-expand"></i>
</button> </button>
<button class="panorama-modal-btn" id="photosphere-help-btn">
<i class="fas fa-question-circle"></i>
</button>
</div> </div>
</div> </div>
<button class="panorama-modal-close" id="pannellum-modal-close"> <button class="panorama-modal-close" id="photosphere-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">
<p><strong>360° VR 조작법</strong></p>
<p>🖱 드래그: 전방향 회전</p>
<p>📱 터치: 터치 드래그</p>
<p>🖱 : /아웃</p>
<p>📱 핀치: /아웃</p>
<p> 화살표: 방향 이동</p>
<p> +/-: </p>
<p> ESC: 닫기</p>
</div>
</div> </div>
</div> </div>
`; `;
document.body.insertAdjacentHTML('beforeend', modalHTML); document.body.insertAdjacentHTML('beforeend', modalHTML);
setupPhotoSphereModalListeners();
// 모달 이벤트 리스너 설정
setupPannellumModalListeners();
} }
function setupPannellumModalListeners() { function setupPhotoSphereModalListeners() {
const modal = document.getElementById('pannellum-modal'); const modal = document.getElementById('photosphere-modal');
const closeBtn = document.getElementById('pannellum-modal-close'); const closeBtn = document.getElementById('photosphere-modal-close');
const resetBtn = document.getElementById('pannellum-reset-btn'); const resetBtn = document.getElementById('photosphere-reset-btn');
const autoBtn = document.getElementById('pannellum-auto-btn'); const autoBtn = document.getElementById('photosphere-auto-btn');
const fullscreenBtn = document.getElementById('pannellum-fullscreen-btn'); const fullscreenBtn = document.getElementById('photosphere-fullscreen-btn');
const helpBtn = document.getElementById('photosphere-help-btn');
const helpText = document.getElementById('photosphere-help-text');
let isAutoRotating = false; let isAutoRotating = false;
let helpVisible = false;
// 모달 닫기 이벤트 // 모달 닫기
closeBtn.addEventListener('click', closePannellumModal); closeBtn.addEventListener('click', closePhotoSphereModal);
modal.addEventListener('click', (e) => {
if (e.target === modal) closePhotoSphereModal();
});
// 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')) {
closePannellumModal(); closePhotoSphereModal();
} }
}); });
// 리셋 버튼 // 리셋 버튼
resetBtn.addEventListener('click', () => { resetBtn.addEventListener('click', () => {
if (currentPanoramaViewer) { if (currentPhotoSphereViewer) {
currentPanoramaViewer.setPitch(0); currentPhotoSphereViewer.animate({
currentPanoramaViewer.setYaw(0); yaw: 0,
stopAutoRotate(); pitch: 0,
zoom: 50,
speed: '2rpm'
});
} }
}); });
// 자동 회전 버튼 // 자동 회전 버튼
autoBtn.addEventListener('click', () => { autoBtn.addEventListener('click', () => {
if (currentPhotoSphereViewer) {
if (isAutoRotating) { if (isAutoRotating) {
stopAutoRotate(); currentPhotoSphereViewer.stopAutorotate();
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전';
autoBtn.classList.remove('active');
isAutoRotating = false;
} else { } else {
startAutoRotate(); currentPhotoSphereViewer.startAutorotate();
autoBtn.innerHTML = '<i class="fas fa-pause"></i> 정지';
autoBtn.classList.add('active');
isAutoRotating = true;
}
} }
}); });
// 전체화면 버튼 // 전체화면 버튼
fullscreenBtn.addEventListener('click', () => { fullscreenBtn.addEventListener('click', () => {
if (currentPanoramaViewer) { if (currentPhotoSphereViewer) {
currentPanoramaViewer.toggleFullscreen(); currentPhotoSphereViewer.enterFullscreen();
} }
}); });
function startAutoRotate() { // 도움말 버튼
if (currentPanoramaViewer) { helpBtn.addEventListener('click', () => {
currentPanoramaViewer.startAutoRotate(0.5); // 초당 0.5도 회전 helpVisible = !helpVisible;
isAutoRotating = true; helpText.style.display = helpVisible ? 'block' : 'none';
autoBtn.innerHTML = '<i class="fas fa-pause"></i> 정지'; helpBtn.classList.toggle('active', helpVisible);
autoBtn.classList.add('active'); });
}
// 도움말 자동 숨김
setTimeout(() => {
helpText.style.display = 'none';
}, 6000);
} }
function stopAutoRotate() { function openPhotoSphereModal(imageSrc, title) {
if (currentPanoramaViewer) { const modal = document.getElementById('photosphere-modal');
currentPanoramaViewer.stopAutoRotate(); const modalTitle = document.getElementById('photosphere-modal-title');
isAutoRotating = false; const viewerContainer = document.getElementById('photosphere-viewer');
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; modalTitle.textContent = title;
modal.classList.add('active'); modal.classList.add('active');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
// Pannellum 뷰어 초기화 // Photo Sphere Viewer 생성
currentPanoramaViewer = pannellum.viewer('pannellum-viewer', { try {
type: 'equirectangular', currentPhotoSphereViewer = new PhotoSphereViewer.Viewer({
container: viewerContainer,
panorama: imageSrc, panorama: imageSrc,
autoLoad: true, caption: title,
autoRotate: 0, loadingImg: imageSrc, // 로딩 중 미리보기
compass: true, touchmoveTwoFingers: true,
northOffset: 0, mousewheelCtrlKey: false,
preview: imageSrc, defaultYaw: 0,
hfov: 100, defaultPitch: 0,
minHfov: 50, defaultZoomLvl: 50,
maxHfov: 120, minFov: 30,
pitch: 0, maxFov: 90,
yaw: 0, autorotateDelay: null,
mouseZoom: true, autorotateIdle: false,
keyboardZoom: true, autorotateSpeed: '2rpm',
draggable: true, fisheye: false,
disableKeyboardCtrl: false, navbar: [
showControls: false, 'zoom',
showFullscreenCtrl: false, 'move',
showZoomCtrl: false, 'fullscreen',
hotSpotDebug: false, {
backgroundColor: [0, 0, 0], title: '자동회전',
orientationOnByDefault: false content: '🔄',
}); onClick: () => {
const autoBtn = document.getElementById('photosphere-auto-btn');
// 뷰어 로드 완료 이벤트
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(); autoBtn.click();
} }
}
]
});
// 이벤트 리스너
currentPhotoSphereViewer.addEventListener('ready', () => {
console.log('Photo Sphere Viewer 준비 완료');
});
currentPhotoSphereViewer.addEventListener('autorotate', () => {
console.log('자동 회전 시작');
});
currentPhotoSphereViewer.addEventListener('autorotate-stop', () => {
console.log('자동 회전 정지');
});
} catch (error) {
console.error('Photo Sphere Viewer 초기화 실패:', error);
alert('360도 뷰어를 로드할 수 없습니다. 페이지를 새로고침해주세요.');
}
}
function closePhotoSphereModal() {
const modal = document.getElementById('photosphere-modal');
const autoBtn = document.getElementById('photosphere-auto-btn');
// 뷰어 정리 // 뷰어 정리
if (currentPanoramaViewer) { if (currentPhotoSphereViewer) {
currentPanoramaViewer.destroy(); currentPhotoSphereViewer.destroy();
currentPanoramaViewer = null; currentPhotoSphereViewer = null;
} }
// 버튼 초기화
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전';
autoBtn.classList.remove('active');
modal.classList.remove('active'); modal.classList.remove('active');
document.body.style.overflow = ''; document.body.style.overflow = '';
// 뷰어 컨테이너 초기화 // 컨테이너 정리
document.getElementById('pannellum-viewer').innerHTML = ''; document.getElementById('photosphere-viewer').innerHTML = '';
} }