From d53f581c585c78a7abfb6526d7d6be5cc3ab2010 Mon Sep 17 00:00:00 2001 From: gahusb Date: Mon, 26 Jan 2026 02:07:33 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=AC=ED=96=89=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=ED=94=84=EB=A1=A0=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/travel/Travel.css | 38 ++++++++++++--- src/pages/travel/Travel.jsx | 93 ++++++++++++++++++++----------------- 2 files changed, 83 insertions(+), 48 deletions(-) diff --git a/src/pages/travel/Travel.css b/src/pages/travel/Travel.css index b3fc7b7..078ff23 100644 --- a/src/pages/travel/Travel.css +++ b/src/pages/travel/Travel.css @@ -303,9 +303,13 @@ .travel-modal__frame { width: 100%; + height: 68vh; max-height: 68vh; display: grid; place-items: center; + overflow: hidden; + background: rgba(8, 10, 16, 0.3); + border-radius: 16px; } .travel-modal__image { @@ -313,7 +317,14 @@ max-height: 68vh; object-fit: contain; border-radius: 14px; - animation: travel-slide 260ms ease; +} + +.travel-modal__image.is-next { + animation: travel-slide-next 280ms ease; +} + +.travel-modal__image.is-prev { + animation: travel-slide-prev 280ms ease; } .travel-modal__strip { @@ -387,14 +398,28 @@ border-color: rgba(255, 255, 255, 0.5); } -@keyframes travel-slide { +.travel-modal__arrow:disabled { + cursor: not-allowed; + opacity: 0.4; + transform: none; + border-color: rgba(255, 255, 255, 0.15); +} + +@keyframes travel-slide-next { from { - opacity: 0.6; - transform: translateY(8px) scale(0.98); + transform: translateX(20px) scale(0.98); } to { - opacity: 1; - transform: translateY(0) scale(1); + transform: translateX(0) scale(1); + } +} + +@keyframes travel-slide-prev { + from { + transform: translateX(-20px) scale(0.98); + } + to { + transform: translateX(0) scale(1); } } @@ -409,6 +434,7 @@ } .travel-modal__frame { + height: 56vh; max-height: 56vh; } diff --git a/src/pages/travel/Travel.jsx b/src/pages/travel/Travel.jsx index 8aa1ea8..4622e81 100644 --- a/src/pages/travel/Travel.jsx +++ b/src/pages/travel/Travel.jsx @@ -172,7 +172,9 @@ const Travel = () => { const [regionsGeojson, setRegionsGeojson] = useState(null); const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(null); const [modalOffset, setModalOffset] = useState(24); - const [touchStartX, setTouchStartX] = useState(null); + const touchStartXRef = useRef(null); + const [slideDirection, setSlideDirection] = useState('next'); + const [slideToken, setSlideToken] = useState(0); const [page, setPage] = useState(1); const [hasNext, setHasNext] = useState(true); const cacheRef = useRef(new Map()); @@ -366,6 +368,28 @@ const Travel = () => { } }, [hasNext, loading, loadingMore, page, photoSummary, selectedRegion]); + const bumpSlide = (direction) => { + setSlideDirection(direction); + setSlideToken((prev) => prev + 1); + }; + + const goPrev = useCallback(() => { + if (selectedPhotoIndex === null || selectedPhotoIndex <= 0) return; + bumpSlide('prev'); + setSelectedPhotoIndex(selectedPhotoIndex - 1); + }, [selectedPhotoIndex]); + + const goNext = useCallback(() => { + if ( + selectedPhotoIndex === null || + selectedPhotoIndex >= photos.length - 1 + ) { + return; + } + bumpSlide('next'); + setSelectedPhotoIndex(selectedPhotoIndex + 1); + }, [photos.length, selectedPhotoIndex]); + useEffect(() => { if (selectedPhotoIndex === null) return undefined; @@ -375,45 +399,34 @@ const Travel = () => { return; } if (event.key === 'ArrowLeft') { - setSelectedPhotoIndex((prev) => - prev === null - ? prev - : (prev - 1 + photos.length) % photos.length - ); + goPrev(); } if (event.key === 'ArrowRight') { - setSelectedPhotoIndex((prev) => - prev === null ? prev : (prev + 1) % photos.length - ); + goNext(); } }; const handleTouchStart = (event) => { if (selectedPhotoIndex === null) return; const touch = event.touches[0]; - setTouchStartX(touch.clientX); + touchStartXRef.current = touch.clientX; }; const handleTouchEnd = (event) => { - if (selectedPhotoIndex === null || touchStartX === null) return; + if (selectedPhotoIndex === null || touchStartXRef.current === null) + return; const touch = event.changedTouches[0]; - const deltaX = touch.clientX - touchStartX; + const deltaX = touch.clientX - touchStartXRef.current; if (Math.abs(deltaX) > 50) { // 스와이프 거리 임계값 if (deltaX > 0) { // 왼쪽으로 스와이프: 이전 사진 - setSelectedPhotoIndex((prev) => - prev === null - ? prev - : (prev - 1 + photos.length) % photos.length - ); + goPrev(); } else { // 오른쪽으로 스와이프: 다음 사진 - setSelectedPhotoIndex((prev) => - prev === null ? prev : (prev + 1) % photos.length - ); + goNext(); } } - setTouchStartX(null); + touchStartXRef.current = null; }; window.addEventListener('keydown', handleKeyDown); @@ -424,7 +437,7 @@ const Travel = () => { window.removeEventListener('touchstart', handleTouchStart); window.removeEventListener('touchend', handleTouchEnd); }; - }, [photos.length, selectedPhotoIndex]); + }, [goNext, goPrev, selectedPhotoIndex]); useEffect(() => { if (selectedPhotoIndex === null) return undefined; @@ -441,6 +454,11 @@ const Travel = () => { : getStripRange(photos.length, selectedPhotoIndex); const handleSelectPhoto = (index, event) => { + if (selectedPhotoIndex === null) { + bumpSlide('next'); + } else if (index !== selectedPhotoIndex) { + bumpSlide(index > selectedPhotoIndex ? 'next' : 'prev'); + } if (event) { const pointY = typeof event.clientY === 'number' @@ -571,26 +589,20 @@ const Travel = () => {
{