diff --git a/css/gallery.css b/css/gallery.css index 54d5fd8..8465c2e 100644 --- a/css/gallery.css +++ b/css/gallery.css @@ -440,7 +440,6 @@ height: 100vh; background: rgba(0, 0, 0, 0.95); z-index: 2000; - backdrop-filter: blur(5px); } .panorama-modal.active { @@ -515,7 +514,6 @@ display: flex; justify-content: space-between; align-items: center; - backdrop-filter: blur(10px); z-index: 10; } @@ -549,7 +547,6 @@ font-size: 0.875rem; font-weight: 500; transition: all 0.3s ease; - backdrop-filter: blur(10px); min-width: 80px; font-family: inherit; } @@ -594,7 +591,6 @@ justify-content: center; font-size: 20px; transition: all 0.3s ease; - backdrop-filter: blur(10px); z-index: 100; font-family: Arial, sans-serif; } @@ -651,7 +647,6 @@ max-width: 500px; width: 90%; z-index: 200; - 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); @@ -696,10 +691,8 @@ /* 서버 호환성을 위한 추가 스타일 */ .panorama-modal { - /* 서버에서 배경 블러 지원 안될 때 대비 */ + /* 깔끔한 배경 - 블러 효과 제거 */ background: rgba(0, 0, 0, 0.95) !important; - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); } .panorama-viewer-container { @@ -728,45 +721,69 @@ padding: 3rem 0; margin: 3rem 0; } - + .panorama-section h2 { font-size: 2rem; } - + .panorama-grid { grid-template-columns: 1fr; gap: 1.5rem; padding: 0 15px; } - + .panorama-container { height: 250px; } - + + /* 모바일 360도 뷰어 컨테이너 최적화 - 경계 문제 해결 */ + .panorama-viewer-container { + /* GPU 가속 활성화로 부드러운 렌더링 */ + transform: translateZ(0); + -webkit-transform: translateZ(0); + will-change: transform; + /* 터치 시 확대/축소 제어 */ + touch-action: pan-x pinch-zoom; + -webkit-overflow-scrolling: touch; + /* 경계 부분 매끄럽게 처리 */ + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + } + + /* 모바일 줌 시 이미지 품질 개선 */ + .panorama-viewer-container img { + /* 확대 시에도 선명한 이미지 유지 */ + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + /* 경계 부분 부드럽게 처리 */ + backface-visibility: hidden; + -webkit-backface-visibility: hidden; + } + .panorama-modal-close { width: 44px; height: 44px; top: 20px; right: 20px; } - + .panorama-modal-close .close-icon::before, .panorama-modal-close .close-icon::after { width: 16px; } - + .panorama-modal-controls { padding: 1.5rem; flex-direction: column; gap: 1rem; align-items: center; } - + .panorama-control-buttons { gap: 1rem; justify-content: center; } - + .panorama-btn { min-width: 70px; padding: 0.75rem 1rem; diff --git a/js/gallery.js b/js/gallery.js index f5ce2b5..6f94847 100644 --- a/js/gallery.js +++ b/js/gallery.js @@ -375,25 +375,32 @@ class Easy360Viewer { } 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도를 위해 충분히 큼 + + // 모바일에서 줌 시 경계 문제 해결을 위한 더 정확한 크기 계산 + let imageHeight = containerHeight * this.zoom; + let imageWidth = imageHeight * imageAspectRatio; + + // 360도 이미지는 매우 가로가 길므로 최소 크기 보장 + const minWidth = Math.max(containerWidth * 3, imageWidth); + imageWidth = minWidth; + imageHeight = imageWidth / imageAspectRatio; + + // 줌 레벨에 따른 크기 조정 (모바일 확대 시 경계 문제 해결) + if (this.zoom > 1) { + imageHeight = Math.max(containerHeight, imageHeight); + imageWidth = imageHeight * imageAspectRatio; } - - // 이미지 크기 설정 - 브라우저 호환성 개선 + + // 이미지 크기 설정 - 픽셀 완벽 정렬로 경계 문제 방지 this.image.style.width = Math.round(imageWidth) + 'px'; - this.image.style.height = Math.round(containerHeight) + 'px'; + this.image.style.height = Math.round(imageHeight) + 'px'; this.imageWrapper.style.width = Math.round(imageWidth) + 'px'; this.imageWrapper.style.height = Math.round(containerHeight) + 'px'; - + // 단일 이미지 너비 저장 this.singleImageWidth = imageWidth; } @@ -553,13 +560,17 @@ class Easy360Viewer { updatePosition(currentX) { const deltaX = currentX - this.lastX; - + // 좌우 이동 (드래그 방향과 반대) this.currentX -= deltaX; - - // 속도 계산 (관성용) - this.velocity = -deltaX * 0.1; - + + // 속도 계산 (관성용) - 더 부드러운 관성 적용 + this.velocity = -deltaX * 0.15; + + // 극한 속도 제한 (튀는 현상 방지) + const maxVelocity = this.singleImageWidth * 0.05; + this.velocity = Math.max(-maxVelocity, Math.min(maxVelocity, this.velocity)); + this.updateTransform(); this.lastX = currentX; } @@ -583,32 +594,55 @@ class Easy360Viewer { handleResize() { if (this.image && this.image.complete) { - // 리사이즈 시 비율 유지 - const oldRatio = this.currentX / this.singleImageWidth; + // 리사이즈 시 비율 유지하되, 튀는 현상 방지 + const oldRatio = (this.currentX - this.singleImageWidth) / this.singleImageWidth; + const oldImageWidth = this.singleImageWidth; + this.setupImageSize(); this.createDuplicateImages(); // 이미지 다시 생성 - this.currentX = this.singleImageWidth * oldRatio; // 비율 유지 + + // 정규화된 위치로 복원 (중앙 이미지 기준) + this.currentX = this.singleImageWidth + (this.singleImageWidth * oldRatio); + + // 경계값 확인 및 보정 + if (this.currentX < 0) this.currentX += this.singleImageWidth; + if (this.currentX > this.singleImageWidth * 2) this.currentX -= this.singleImageWidth; + 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 threshold = this.singleImageWidth * 0.1; // 10% 여유 공간 + + // 왼쪽 경계 처리 - 부드러운 전환 + if (this.currentX < -threshold) { + this.currentX = this.singleImageWidth + (this.currentX + threshold); } - - // 부드러운 변환을 위해 소수점 제거 + // 오른쪽 경계 처리 - 부드러운 전환 + else if (this.currentX > this.singleImageWidth * 2 + threshold) { + this.currentX = this.singleImageWidth + (this.currentX - this.singleImageWidth * 2 - threshold); + } + + // 정확한 무한 루프 경계 처리 (튀는 현상 방지) + const normalizedX = ((this.currentX % this.singleImageWidth) + this.singleImageWidth) % this.singleImageWidth; + const actualX = normalizedX + this.singleImageWidth; + + // 현재 위치와 목표 위치의 차이가 큰 경우만 보정 (부드러운 회전 유지) + if (Math.abs(this.currentX - actualX) > this.singleImageWidth * 0.5) { + this.currentX = actualX; + } + + // 고정밀 변환 계산 (소수점 2자리까지 유지하여 부드러움 보장) const translateX = Math.round(-this.currentX * 100) / 100; - const scale = Math.round(this.zoom * 100) / 100; - + const scale = Math.round(this.zoom * 1000) / 1000; + const transform = `translateX(${translateX}px) scale(${scale})`; this.imageWrapper.style.transform = transform; - + // 브라우저 호환성 this.imageWrapper.style.webkitTransform = transform; this.imageWrapper.style.msTransform = transform;