여행 기록 프론트 오류 수정

This commit is contained in:
2026-01-26 02:07:33 +09:00
parent bca9724a4b
commit d53f581c58
2 changed files with 83 additions and 48 deletions

View File

@@ -303,9 +303,13 @@
.travel-modal__frame { .travel-modal__frame {
width: 100%; width: 100%;
height: 68vh;
max-height: 68vh; max-height: 68vh;
display: grid; display: grid;
place-items: center; place-items: center;
overflow: hidden;
background: rgba(8, 10, 16, 0.3);
border-radius: 16px;
} }
.travel-modal__image { .travel-modal__image {
@@ -313,7 +317,14 @@
max-height: 68vh; max-height: 68vh;
object-fit: contain; object-fit: contain;
border-radius: 14px; 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 { .travel-modal__strip {
@@ -387,14 +398,28 @@
border-color: rgba(255, 255, 255, 0.5); 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 { from {
opacity: 0.6; transform: translateX(20px) scale(0.98);
transform: translateY(8px) scale(0.98);
} }
to { to {
opacity: 1; transform: translateX(0) scale(1);
transform: translateY(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 { .travel-modal__frame {
height: 56vh;
max-height: 56vh; max-height: 56vh;
} }

View File

@@ -172,7 +172,9 @@ const Travel = () => {
const [regionsGeojson, setRegionsGeojson] = useState(null); const [regionsGeojson, setRegionsGeojson] = useState(null);
const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(null); const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(null);
const [modalOffset, setModalOffset] = useState(24); 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 [page, setPage] = useState(1);
const [hasNext, setHasNext] = useState(true); const [hasNext, setHasNext] = useState(true);
const cacheRef = useRef(new Map()); const cacheRef = useRef(new Map());
@@ -366,6 +368,28 @@ const Travel = () => {
} }
}, [hasNext, loading, loadingMore, page, photoSummary, selectedRegion]); }, [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(() => { useEffect(() => {
if (selectedPhotoIndex === null) return undefined; if (selectedPhotoIndex === null) return undefined;
@@ -375,45 +399,34 @@ const Travel = () => {
return; return;
} }
if (event.key === 'ArrowLeft') { if (event.key === 'ArrowLeft') {
setSelectedPhotoIndex((prev) => goPrev();
prev === null
? prev
: (prev - 1 + photos.length) % photos.length
);
} }
if (event.key === 'ArrowRight') { if (event.key === 'ArrowRight') {
setSelectedPhotoIndex((prev) => goNext();
prev === null ? prev : (prev + 1) % photos.length
);
} }
}; };
const handleTouchStart = (event) => { const handleTouchStart = (event) => {
if (selectedPhotoIndex === null) return; if (selectedPhotoIndex === null) return;
const touch = event.touches[0]; const touch = event.touches[0];
setTouchStartX(touch.clientX); touchStartXRef.current = touch.clientX;
}; };
const handleTouchEnd = (event) => { const handleTouchEnd = (event) => {
if (selectedPhotoIndex === null || touchStartX === null) return; if (selectedPhotoIndex === null || touchStartXRef.current === null)
return;
const touch = event.changedTouches[0]; const touch = event.changedTouches[0];
const deltaX = touch.clientX - touchStartX; const deltaX = touch.clientX - touchStartXRef.current;
if (Math.abs(deltaX) > 50) { // 스와이프 거리 임계값 if (Math.abs(deltaX) > 50) { // 스와이프 거리 임계값
if (deltaX > 0) { if (deltaX > 0) {
// 왼쪽으로 스와이프: 이전 사진 // 왼쪽으로 스와이프: 이전 사진
setSelectedPhotoIndex((prev) => goPrev();
prev === null
? prev
: (prev - 1 + photos.length) % photos.length
);
} else { } else {
// 오른쪽으로 스와이프: 다음 사진 // 오른쪽으로 스와이프: 다음 사진
setSelectedPhotoIndex((prev) => goNext();
prev === null ? prev : (prev + 1) % photos.length
);
} }
} }
setTouchStartX(null); touchStartXRef.current = null;
}; };
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
@@ -424,7 +437,7 @@ const Travel = () => {
window.removeEventListener('touchstart', handleTouchStart); window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('touchend', handleTouchEnd); window.removeEventListener('touchend', handleTouchEnd);
}; };
}, [photos.length, selectedPhotoIndex]); }, [goNext, goPrev, selectedPhotoIndex]);
useEffect(() => { useEffect(() => {
if (selectedPhotoIndex === null) return undefined; if (selectedPhotoIndex === null) return undefined;
@@ -441,6 +454,11 @@ const Travel = () => {
: getStripRange(photos.length, selectedPhotoIndex); : getStripRange(photos.length, selectedPhotoIndex);
const handleSelectPhoto = (index, event) => { const handleSelectPhoto = (index, event) => {
if (selectedPhotoIndex === null) {
bumpSlide('next');
} else if (index !== selectedPhotoIndex) {
bumpSlide(index > selectedPhotoIndex ? 'next' : 'prev');
}
if (event) { if (event) {
const pointY = const pointY =
typeof event.clientY === 'number' typeof event.clientY === 'number'
@@ -571,26 +589,20 @@ const Travel = () => {
<button <button
type="button" type="button"
className="travel-modal__arrow is-prev" className="travel-modal__arrow is-prev"
onClick={() => onClick={goPrev}
setSelectedPhotoIndex((prev) => disabled={selectedPhotoIndex === 0}
prev === null
? prev
: (prev - 1 + photos.length) %
photos.length
)
}
aria-label="Previous" aria-label="Previous"
> >
{'<'} {'<'}
</button> </button>
<div className="travel-modal__frame"> <div className="travel-modal__frame">
<img <img
key={ key={`${selectedPhotoIndex}-${slideToken}`}
photos[selectedPhotoIndex]?.original || className={`travel-modal__image ${
photos[selectedPhotoIndex]?.src || slideDirection === 'prev'
selectedPhotoIndex ? 'is-prev'
} : 'is-next'
className="travel-modal__image" }`}
src={ src={
photos[selectedPhotoIndex]?.original || photos[selectedPhotoIndex]?.original ||
photos[selectedPhotoIndex]?.src photos[selectedPhotoIndex]?.src
@@ -609,12 +621,9 @@ const Travel = () => {
<button <button
type="button" type="button"
className="travel-modal__arrow is-next" className="travel-modal__arrow is-next"
onClick={() => onClick={goNext}
setSelectedPhotoIndex((prev) => disabled={
prev === null selectedPhotoIndex === photos.length - 1
? prev
: (prev + 1) % photos.length
)
} }
aria-label="Next" aria-label="Next"
> >