Fix : 갤러리 업데이트
This commit is contained in:
parent
e50cfa698f
commit
243e3b3bc0
@ -7,7 +7,9 @@
|
|||||||
"Bash(sed:*)",
|
"Bash(sed:*)",
|
||||||
"Bash(sort:*)",
|
"Bash(sort:*)",
|
||||||
"WebFetch(domain:studio.v-llage.com)",
|
"WebFetch(domain:studio.v-llage.com)",
|
||||||
"Bash(awk:*)"
|
"Bash(awk:*)",
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(start http://localhost:8000/gallery.html)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
285
css/gallery.css
285
css/gallery.css
@ -412,7 +412,7 @@
|
|||||||
20%, 80% { opacity: 1; }
|
20%, 80% { opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 개선된 360도 이미지 전체화면 모달 */
|
/* 새로운 360도 파노라마 모달 */
|
||||||
.panorama-modal {
|
.panorama-modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -422,7 +422,7 @@
|
|||||||
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;
|
backdrop-filter: blur(5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal.active {
|
.panorama-modal.active {
|
||||||
@ -431,111 +431,153 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panorama-modal-content {
|
.panorama-modal-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 90vw;
|
width: 95vw;
|
||||||
height: 70vh;
|
height: 90vh;
|
||||||
max-width: 1200px;
|
max-width: 1400px;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.7);
|
||||||
|
background: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-viewer {
|
/* 새로운 360도 뷰어 컨테이너 */
|
||||||
|
.panorama-viewer-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
background: #000;
|
background: #000;
|
||||||
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-image {
|
.panorama-viewer-container:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 360도 뷰어 로딩 스피너 */
|
||||||
|
.panorama-loader {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 50%;
|
||||||
left: 0;
|
left: 50%;
|
||||||
height: 100%;
|
transform: translate(-50%, -50%);
|
||||||
object-fit: cover;
|
text-align: center;
|
||||||
transition: transform 0.1s ease-out;
|
color: var(--text-white);
|
||||||
will-change: transform;
|
z-index: 10;
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panorama-loader .spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top: 3px solid var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-loader p {
|
||||||
|
font-size: var(--font-base);
|
||||||
|
opacity: 0.8;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 새로운 모달 컨트롤 */
|
||||||
.panorama-modal-controls {
|
.panorama-modal-controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.9));
|
background: linear-gradient(transparent, rgba(0, 0, 0, 0.9));
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl) var(--spacing-2xl);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-title {
|
.panorama-modal-title {
|
||||||
color: var(--text-white);
|
color: var(--text-white);
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
font-size: var(--font-xl);
|
font-size: var(--font-2xl);
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-buttons {
|
/* 새로운 컨트롤 버튼 스타일 */
|
||||||
|
.panorama-control-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-lg);
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-btn {
|
.panorama-btn {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
display: flex;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
color: var(--text-white);
|
color: var(--text-white);
|
||||||
padding: var(--spacing-sm) var(--spacing-lg);
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: var(--font-base);
|
font-size: var(--font-sm);
|
||||||
transition: var(--transition);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-btn:hover {
|
.panorama-btn:hover {
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-3px) scale(1.05);
|
||||||
|
box-shadow: 0 8px 20px rgba(255, 136, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-btn.active {
|
.panorama-btn.active {
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 4px 15px rgba(255, 136, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panorama-btn i {
|
||||||
|
font-size: var(--font-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-btn span {
|
||||||
|
font-size: var(--font-xs);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 새로운 닫기 버튼 */
|
||||||
.panorama-modal-close {
|
.panorama-modal-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--spacing-lg);
|
top: var(--spacing-xl);
|
||||||
right: var(--spacing-lg);
|
right: var(--spacing-xl);
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.8);
|
||||||
color: var(--text-white);
|
color: var(--text-white);
|
||||||
width: 50px;
|
width: 60px;
|
||||||
height: 50px;
|
height: 60px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: var(--font-xl);
|
font-size: var(--font-2xl);
|
||||||
transition: var(--transition);
|
transition: all 0.3s ease;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-close:hover {
|
.panorama-modal-close:hover {
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
transform: scale(1.1);
|
transform: scale(1.1) rotate(90deg);
|
||||||
|
box-shadow: 0 8px 20px rgba(255, 136, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-close::before {
|
.panorama-modal-close::before {
|
||||||
@ -543,60 +585,63 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-help-text {
|
/* 새로운 도움말 패널 */
|
||||||
|
.panorama-help-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--spacing-xl);
|
top: 50%;
|
||||||
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 {
|
|
||||||
position: absolute;
|
|
||||||
top: var(--spacing-lg);
|
|
||||||
left: var(--spacing-lg);
|
|
||||||
background: rgba(255, 136, 0, 0.9);
|
|
||||||
color: var(--text-white);
|
|
||||||
padding: var(--spacing-sm) var(--spacing-lg);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
font-size: var(--font-base);
|
|
||||||
font-weight: 500;
|
|
||||||
animation: fadeInOut 4s ease-in-out;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.panorama-modal-indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: var(--spacing-lg);
|
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translate(-50%, -50%);
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.95);
|
||||||
color: var(--text-white);
|
color: var(--text-white);
|
||||||
padding: var(--spacing-sm) var(--spacing-lg);
|
padding: var(--spacing-2xl);
|
||||||
border-radius: var(--border-radius-full);
|
border-radius: var(--border-radius-lg);
|
||||||
font-size: var(--font-base);
|
max-width: 500px;
|
||||||
font-weight: 600;
|
width: 90%;
|
||||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
z-index: 200;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(20px);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
display: none;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panorama-help-panel h3 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
margin: 0 0 var(--spacing-lg) 0;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border-left: 3px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-item i {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: var(--font-lg);
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-item span {
|
||||||
|
font-size: var(--font-base);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 기존 스타일 제거됨 - 새로운 도움말 패널로 대체 */
|
||||||
|
|
||||||
/* 반응형 디자인 */
|
/* 반응형 디자인 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.page-header h1 {
|
.page-header h1 {
|
||||||
@ -709,36 +754,64 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-content {
|
.panorama-modal-content {
|
||||||
width: 95vw;
|
width: 98vw;
|
||||||
height: 60vh;
|
height: 85vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-controls {
|
.panorama-modal-controls {
|
||||||
padding: var(--spacing-lg);
|
padding: var(--spacing-lg);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-title {
|
.panorama-modal-title {
|
||||||
|
font-size: var(--font-lg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-control-buttons {
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-btn {
|
||||||
|
min-width: 70px;
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-btn i {
|
||||||
font-size: var(--font-base);
|
font-size: var(--font-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-btn {
|
.panorama-btn span {
|
||||||
font-size: var(--font-sm);
|
font-size: 10px;
|
||||||
padding: var(--spacing-xs) var(--spacing-md);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-close {
|
.panorama-modal-close {
|
||||||
width: 40px;
|
width: 50px;
|
||||||
height: 40px;
|
height: 50px;
|
||||||
font-size: var(--font-base);
|
font-size: var(--font-lg);
|
||||||
|
top: var(--spacing-md);
|
||||||
|
right: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-help {
|
.panorama-help-panel {
|
||||||
font-size: var(--font-sm);
|
width: 95%;
|
||||||
padding: var(--spacing-xs) var(--spacing-md);
|
padding: var(--spacing-lg);
|
||||||
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panorama-modal-indicator {
|
.panorama-help-panel h3 {
|
||||||
|
font-size: var(--font-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-item {
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-item span {
|
||||||
font-size: var(--font-sm);
|
font-size: var(--font-sm);
|
||||||
padding: var(--spacing-xs) var(--spacing-md);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,8 +40,6 @@
|
|||||||
<!-- 아이콘 폰트 (이모지 대체용) -->
|
<!-- 아이콘 폰트 (이모지 대체용) -->
|
||||||
<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">
|
||||||
|
|
||||||
<!-- A-Frame VR Framework -->
|
|
||||||
<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">
|
||||||
|
|||||||
707
js/gallery.js
707
js/gallery.js
@ -6,7 +6,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
initGallery();
|
initGallery();
|
||||||
initLightbox();
|
initLightbox();
|
||||||
initGalleryAnimations();
|
initGalleryAnimations();
|
||||||
initAFrame360Viewers();
|
initCustom360Viewers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 갤러리 초기화
|
// 갤러리 초기화
|
||||||
@ -267,268 +267,605 @@ function handleSwipe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// A-Frame 기반 360도 VR 뷰어 - 안정적이고 강력한 VR 체험
|
// 간단한 360도 파노라마 뷰어 - 좌우 스크롤 방식
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
let currentAFrameScene = null;
|
class Easy360Viewer {
|
||||||
let autoRotationInterval = null;
|
constructor(container, imageSrc, title) {
|
||||||
let isAutoRotating = false;
|
this.container = container;
|
||||||
|
this.imageSrc = imageSrc;
|
||||||
|
this.title = title;
|
||||||
|
|
||||||
function initAFrame360Viewers() {
|
// 뷰어 상태
|
||||||
// 모달 생성
|
this.currentX = 0; // 현재 X 위치
|
||||||
createAFrameModal();
|
this.maxX = 0; // 최대 스크롤 가능 X
|
||||||
|
this.zoom = 1;
|
||||||
|
|
||||||
// 미리보기 설정
|
// 마우스/터치 상태
|
||||||
initAFramePreviews();
|
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;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 로딩 표시
|
||||||
|
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;
|
||||||
|
`;
|
||||||
|
this.image.draggable = false;
|
||||||
|
|
||||||
|
// 이미지 로드 완료 후
|
||||||
|
this.image.onload = () => {
|
||||||
|
this.hideLoading();
|
||||||
|
this.setupImageSize();
|
||||||
|
this.createDuplicateImages();
|
||||||
|
this.updateTransform();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.image.onerror = () => {
|
||||||
|
this.hideLoading();
|
||||||
|
this.showError();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.imageWrapper.appendChild(this.image);
|
||||||
|
this.container.appendChild(this.imageWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupImageSize() {
|
||||||
|
// 이미지 자연 어숙비를 유지하면서 컨테이너 높이에 맞춤
|
||||||
|
const containerHeight = this.container.clientHeight;
|
||||||
|
const imageAspectRatio = this.image.naturalWidth / this.image.naturalHeight;
|
||||||
|
const imageWidth = containerHeight * imageAspectRatio;
|
||||||
|
|
||||||
|
// 360도 이미지는 보통 매우 가로가 김
|
||||||
|
this.imageWrapper.style.width = imageWidth + 'px';
|
||||||
|
|
||||||
|
// 스크롤 가능 범위 계산
|
||||||
|
this.maxX = Math.max(0, imageWidth - this.container.clientWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
this.imageWrapper.appendChild(imageClone1);
|
||||||
|
this.imageWrapper.appendChild(imageClone2);
|
||||||
|
|
||||||
|
// 래퍼 전체 너비를 3배로
|
||||||
|
const currentWidth = parseInt(this.imageWrapper.style.width);
|
||||||
|
this.imageWrapper.style.width = (currentWidth * 3) + 'px';
|
||||||
|
|
||||||
|
// 스크롤 범위 업데이트
|
||||||
|
this.maxX = currentWidth * 2;
|
||||||
|
|
||||||
|
// 시작 위치를 중앙 이미지로
|
||||||
|
this.currentX = currentWidth;
|
||||||
|
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) {
|
||||||
|
this.setupImageSize();
|
||||||
|
this.updateTransform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTransform() {
|
||||||
|
if (!this.imageWrapper) return;
|
||||||
|
|
||||||
|
// 무한 스크롤 처리
|
||||||
|
const singleImageWidth = parseInt(this.imageWrapper.style.width) / 3;
|
||||||
|
|
||||||
|
if (this.currentX < 0) {
|
||||||
|
this.currentX += singleImageWidth;
|
||||||
|
} else if (this.currentX > singleImageWidth * 2) {
|
||||||
|
this.currentX -= singleImageWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transform = `translateX(${-this.currentX}px) scale(${this.zoom})`;
|
||||||
|
this.imageWrapper.style.transform = 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 = parseInt(this.imageWrapper.style.width) / 3; // 중앙 이미지로
|
||||||
|
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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAFramePreviews() {
|
let current360Viewer = null;
|
||||||
const containers = document.querySelectorAll('.panorama-clickable');
|
|
||||||
|
|
||||||
containers.forEach(container => {
|
function initCustom360Viewers() {
|
||||||
const imageSrc = container.dataset.image;
|
createPanoramaModal();
|
||||||
const title = container.dataset.title;
|
initPanoramaPreviews();
|
||||||
|
}
|
||||||
|
|
||||||
// 클릭 이벤트
|
function initPanoramaPreviews() {
|
||||||
container.addEventListener('click', () => {
|
const clickableElements = document.querySelectorAll('.panorama-clickable');
|
||||||
openAFrameModal(imageSrc, title);
|
|
||||||
|
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(() => {
|
setTimeout(() => {
|
||||||
const help = container.querySelector('.panorama-help');
|
const helpText = element.querySelector('.panorama-help');
|
||||||
if (help) {
|
if (helpText) {
|
||||||
help.style.opacity = '0';
|
helpText.style.opacity = '0';
|
||||||
setTimeout(() => help.remove(), 1000);
|
setTimeout(() => helpText.remove(), 500);
|
||||||
}
|
}
|
||||||
}, 4000);
|
}, 4000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAFrameModal() {
|
function createPanoramaModal() {
|
||||||
const modalHTML = `
|
const modalHTML = `
|
||||||
<div id="aframe-modal" class="panorama-modal">
|
<div id="panorama-modal" class="panorama-modal">
|
||||||
<div class="panorama-modal-content">
|
<div class="panorama-modal-content">
|
||||||
<div id="aframe-viewer" class="panorama-modal-viewer">
|
<div id="panorama-viewer-container" class="panorama-viewer-container">
|
||||||
<!-- A-Frame Scene will be injected here -->
|
<!-- 360도 뷰어가 여기에 들어갑니다 -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panorama-modal-controls">
|
<div class="panorama-modal-controls">
|
||||||
<div id="aframe-modal-title" class="panorama-modal-title"></div>
|
<div id="panorama-modal-title" class="panorama-modal-title"></div>
|
||||||
<div class="panorama-modal-buttons">
|
<div class="panorama-control-buttons">
|
||||||
<button class="panorama-modal-btn" id="aframe-reset-btn">
|
<button class="panorama-btn" id="panorama-reset-btn">
|
||||||
<i class="fas fa-home"></i> 리셋
|
<i class="fas fa-home"></i>
|
||||||
|
<span>초기화</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="panorama-modal-btn" id="aframe-auto-btn">
|
<button class="panorama-btn" id="panorama-auto-btn">
|
||||||
<i class="fas fa-sync-alt"></i> 자동회전
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
<span>자동회전</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="panorama-modal-btn" id="aframe-vr-btn">
|
<button class="panorama-btn" id="panorama-zoom-in-btn">
|
||||||
<i class="fas fa-vr-cardboard"></i> VR모드
|
<i class="fas fa-search-plus"></i>
|
||||||
|
<span>확대</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="panorama-modal-btn" id="aframe-help-btn">
|
<button class="panorama-btn" id="panorama-zoom-out-btn">
|
||||||
<i class="fas fa-question-circle"></i> 도움말
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="panorama-modal-close" id="aframe-modal-close">
|
|
||||||
|
<button class="panorama-modal-close" id="panorama-close-btn">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="panorama-modal-help-text" id="aframe-help-text">
|
|
||||||
<p><strong>360° VR 조작법</strong></p>
|
<div class="panorama-help-panel" id="panorama-help-panel">
|
||||||
<p>🖱️ 드래그: 전방향 회전</p>
|
<h3>360° 조작 가이드</h3>
|
||||||
<p>📱 터치: 터치 후 드래그</p>
|
<div class="help-content">
|
||||||
<p>🖱️ 휠: 줌 인/아웃</p>
|
<div class="help-item">
|
||||||
<p>📱 핀치: 줌 인/아웃</p>
|
<i class="fas fa-mouse"></i>
|
||||||
<p>⌨️ WASD: 이동</p>
|
<span>마우스 드래그로 화면을 회전시킬 수 있습니다</span>
|
||||||
<p>📱 VR모드: VR 헤드셋 지원</p>
|
</div>
|
||||||
<p>⌨️ ESC: 닫기</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||||||
setupAFrameModalListeners();
|
setupPanoramaModalEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupAFrameModalListeners() {
|
function setupPanoramaModalEvents() {
|
||||||
const modal = document.getElementById('aframe-modal');
|
const modal = document.getElementById('panorama-modal');
|
||||||
const closeBtn = document.getElementById('aframe-modal-close');
|
const closeBtn = document.getElementById('panorama-close-btn');
|
||||||
const resetBtn = document.getElementById('aframe-reset-btn');
|
const resetBtn = document.getElementById('panorama-reset-btn');
|
||||||
const autoBtn = document.getElementById('aframe-auto-btn');
|
const autoBtn = document.getElementById('panorama-auto-btn');
|
||||||
const vrBtn = document.getElementById('aframe-vr-btn');
|
const zoomInBtn = document.getElementById('panorama-zoom-in-btn');
|
||||||
const helpBtn = document.getElementById('aframe-help-btn');
|
const zoomOutBtn = document.getElementById('panorama-zoom-out-btn');
|
||||||
const helpText = document.getElementById('aframe-help-text');
|
const helpBtn = document.getElementById('panorama-help-btn');
|
||||||
|
const helpPanel = document.getElementById('panorama-help-panel');
|
||||||
|
|
||||||
let helpVisible = false;
|
let isAutoRotating = false;
|
||||||
|
let isHelpVisible = false;
|
||||||
|
|
||||||
// 모달 닫기
|
// 모달 닫기
|
||||||
closeBtn.addEventListener('click', closeAFrameModal);
|
closeBtn.addEventListener('click', closePanoramaModal);
|
||||||
modal.addEventListener('click', (e) => {
|
modal.addEventListener('click', (e) => {
|
||||||
if (e.target === modal) closeAFrameModal();
|
if (e.target === modal) closePanoramaModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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')) {
|
||||||
closeAFrameModal();
|
closePanoramaModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 리셋 버튼
|
// 초기화 버튼
|
||||||
resetBtn.addEventListener('click', () => {
|
resetBtn.addEventListener('click', () => {
|
||||||
if (currentAFrameScene) {
|
if (current360Viewer) {
|
||||||
const camera = currentAFrameScene.querySelector('[camera]');
|
current360Viewer.reset();
|
||||||
if (camera) {
|
|
||||||
camera.setAttribute('rotation', '0 0 0');
|
|
||||||
camera.setAttribute('position', '0 0 0');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 자동 회전 버튼
|
// 자동회전 버튼
|
||||||
autoBtn.addEventListener('click', () => {
|
autoBtn.addEventListener('click', () => {
|
||||||
if (isAutoRotating) {
|
if (current360Viewer) {
|
||||||
stopAutoRotation();
|
if (isAutoRotating) {
|
||||||
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전';
|
current360Viewer.stopAutoRotate();
|
||||||
autoBtn.classList.remove('active');
|
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i><span>자동회전</span>';
|
||||||
} else {
|
autoBtn.classList.remove('active');
|
||||||
startAutoRotation();
|
isAutoRotating = false;
|
||||||
autoBtn.innerHTML = '<i class="fas fa-pause"></i> 정지';
|
} else {
|
||||||
autoBtn.classList.add('active');
|
current360Viewer.startAutoRotate();
|
||||||
|
autoBtn.innerHTML = '<i class="fas fa-pause"></i><span>정지</span>';
|
||||||
|
autoBtn.classList.add('active');
|
||||||
|
isAutoRotating = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// VR 버튼
|
// 확대 버튼
|
||||||
vrBtn.addEventListener('click', () => {
|
zoomInBtn.addEventListener('click', () => {
|
||||||
if (currentAFrameScene) {
|
if (current360Viewer) {
|
||||||
const vrButton = currentAFrameScene.querySelector('[vr-mode-ui]');
|
current360Viewer.zoomIn();
|
||||||
if (vrButton) {
|
}
|
||||||
// VR 모드 진입
|
});
|
||||||
currentAFrameScene.enterVR();
|
|
||||||
}
|
// 축소 버튼
|
||||||
|
zoomOutBtn.addEventListener('click', () => {
|
||||||
|
if (current360Viewer) {
|
||||||
|
current360Viewer.zoomOut();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 도움말 버튼
|
// 도움말 버튼
|
||||||
helpBtn.addEventListener('click', () => {
|
helpBtn.addEventListener('click', () => {
|
||||||
helpVisible = !helpVisible;
|
isHelpVisible = !isHelpVisible;
|
||||||
helpText.style.display = helpVisible ? 'block' : 'none';
|
helpPanel.style.display = isHelpVisible ? 'block' : 'none';
|
||||||
helpBtn.classList.toggle('active', helpVisible);
|
helpBtn.classList.toggle('active', isHelpVisible);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 도움말 자동 숨김
|
// 도움말 패널 클릭시 닫기
|
||||||
setTimeout(() => {
|
helpPanel.addEventListener('click', (e) => {
|
||||||
helpText.style.display = 'none';
|
if (e.target === helpPanel) {
|
||||||
}, 6000);
|
isHelpVisible = false;
|
||||||
|
helpPanel.style.display = 'none';
|
||||||
|
helpBtn.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openAFrameModal(imageSrc, title) {
|
function openPanoramaModal(imageSrc, title) {
|
||||||
const modal = document.getElementById('aframe-modal');
|
const modal = document.getElementById('panorama-modal');
|
||||||
const modalTitle = document.getElementById('aframe-modal-title');
|
const modalTitle = document.getElementById('panorama-modal-title');
|
||||||
const viewerContainer = document.getElementById('aframe-viewer');
|
const viewerContainer = document.getElementById('panorama-viewer-container');
|
||||||
|
|
||||||
modalTitle.textContent = title;
|
modalTitle.textContent = title;
|
||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
// A-Frame Scene 생성
|
// 간단한 360도 좌우 스크롤 뷰어 생성
|
||||||
const sceneHTML = `
|
current360Viewer = new Easy360Viewer(viewerContainer, imageSrc, title);
|
||||||
<a-scene
|
|
||||||
id="aframe-scene"
|
|
||||||
embedded
|
|
||||||
style="height: 100%; width: 100%;"
|
|
||||||
vr-mode-ui="enabled: true"
|
|
||||||
background="color: #000000">
|
|
||||||
|
|
||||||
<!-- 360도 이미지 스카이박스 -->
|
|
||||||
<a-sky
|
|
||||||
id="panorama-sky"
|
|
||||||
src="${imageSrc}"
|
|
||||||
rotation="0 -90 0">
|
|
||||||
</a-sky>
|
|
||||||
|
|
||||||
<!-- 카메라 (사용자 시점) -->
|
|
||||||
<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">
|
|
||||||
|
|
||||||
<!-- 커서 (VR 모드용) -->
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- 조명 -->
|
|
||||||
<a-light type="ambient" color="#404040" intensity="0.4"></a-light>
|
|
||||||
<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() {
|
function closePanoramaModal() {
|
||||||
if (!currentAFrameScene) return;
|
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');
|
||||||
|
|
||||||
isAutoRotating = true;
|
// WebGL 뷰어 정리
|
||||||
const camera = currentAFrameScene.querySelector('#panorama-camera');
|
if (current360Viewer) {
|
||||||
let currentRotationY = 0;
|
current360Viewer.destroy();
|
||||||
|
current360Viewer = null;
|
||||||
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 closeAFrameModal() {
|
// UI 초기화
|
||||||
const modal = document.getElementById('aframe-modal');
|
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i><span>자동회전</span>';
|
||||||
const autoBtn = document.getElementById('aframe-auto-btn');
|
|
||||||
|
|
||||||
// 자동 회전 정지
|
|
||||||
stopAutoRotation();
|
|
||||||
|
|
||||||
// 버튼 초기화
|
|
||||||
autoBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 자동회전';
|
|
||||||
autoBtn.classList.remove('active');
|
autoBtn.classList.remove('active');
|
||||||
|
helpBtn.classList.remove('active');
|
||||||
// A-Frame Scene 정리
|
helpPanel.style.display = 'none';
|
||||||
if (currentAFrameScene) {
|
|
||||||
currentAFrameScene.destroy();
|
|
||||||
currentAFrameScene = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.classList.remove('active');
|
modal.classList.remove('active');
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
|
|
||||||
// 컨테이너 정리
|
// 컨테이너 정리
|
||||||
document.getElementById('aframe-viewer').innerHTML = '';
|
document.getElementById('panorama-viewer-container').innerHTML = '';
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user