곡 상세 페이지 트랙 변경 시 스크롤/애니메이션 개선

- ScrollToTop: PC 레이아웃의 main 요소 스크롤 초기화 추가
- TrackDetail: key prop으로 트랙 변경 시 컴포넌트 리마운트
- TrackDetail: main 요소 스크롤 초기화 (PC는 main에서 스크롤)
- 수록곡 선택 시 Link 대신 button + navigate 사용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-24 12:03:33 +09:00
parent eae56df146
commit a7bc2e9800
3 changed files with 41 additions and 9 deletions

View file

@ -16,6 +16,12 @@ function ScrollToTop() {
if (mobileContent) { if (mobileContent) {
mobileContent.scrollTop = 0; mobileContent.scrollTop = 0;
} }
// PC
const main = document.querySelector('main');
if (main) {
main.scrollTop = 0;
}
}, [pathname]); }, [pathname]);
return null; return null;

View file

@ -1,4 +1,4 @@
import { useState, useMemo, useEffect } from 'react'; import { useState, useMemo, useEffect, useLayoutEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom'; import { useParams, useNavigate, Link } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
@ -40,6 +40,11 @@ function MobileTrackDetail() {
const youtubeVideoId = useMemo(() => getYoutubeVideoId(track?.video_url), [track?.video_url]); const youtubeVideoId = useMemo(() => getYoutubeVideoId(track?.video_url), [track?.video_url]);
const videoLabel = track?.video_type === 'special' ? '스페셜 영상' : '뮤직비디오'; const videoLabel = track?.video_type === 'special' ? '스페셜 영상' : '뮤직비디오';
//
useLayoutEffect(() => {
window.scrollTo({ top: 0, behavior: 'instant' });
}, [trackTitle]);
// //
const [showFullLyrics, setShowFullLyrics] = useState(false); const [showFullLyrics, setShowFullLyrics] = useState(false);
@ -93,7 +98,7 @@ function MobileTrackDetail() {
} }
return ( return (
<div className="pb-4"> <div key={trackTitle} className="pb-4">
{/* 트랙 정보 헤더 */} {/* 트랙 정보 헤더 */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}

View file

@ -1,4 +1,4 @@
import { useMemo } from 'react'; import { useMemo, useLayoutEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom'; import { useParams, useNavigate, Link } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
@ -40,6 +40,14 @@ function PCTrackDetail() {
const youtubeVideoId = useMemo(() => getYoutubeVideoId(track?.video_url), [track?.video_url]); const youtubeVideoId = useMemo(() => getYoutubeVideoId(track?.video_url), [track?.video_url]);
const videoLabel = track?.video_type === 'special' ? '스페셜 영상' : '뮤직비디오'; const videoLabel = track?.video_type === 'special' ? '스페셜 영상' : '뮤직비디오';
//
useLayoutEffect(() => {
const main = document.querySelector('main');
if (main) {
main.scrollTop = 0;
}
}, [trackTitle]);
if (loading) { if (loading) {
return ( return (
<motion.div <motion.div
@ -61,7 +69,13 @@ function PCTrackDetail() {
} }
return ( return (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.3 }} className="py-12"> <motion.div
key={trackTitle}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
className="py-12"
>
<div className="max-w-7xl mx-auto px-6"> <div className="max-w-7xl mx-auto px-6">
{/* 브레드크럼 네비게이션 */} {/* 브레드크럼 네비게이션 */}
<div className="flex items-center gap-2 text-sm text-gray-500 mb-8"> <div className="flex items-center gap-2 text-sm text-gray-500 mb-8">
@ -218,11 +232,18 @@ function PCTrackDetail() {
{track.otherTracks?.map((t) => { {track.otherTracks?.map((t) => {
const isCurrent = t.title === track.title; const isCurrent = t.title === track.title;
return ( return (
<Link <button
key={t.id} key={t.id}
to={`/album/${encodeURIComponent(track.album?.title || albumName)}/track/${encodeURIComponent(t.title)}`} type="button"
replace={!isCurrent} onClick={() => {
className={`group flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${ if (!isCurrent) {
navigate(
`/album/${encodeURIComponent(track.album?.title || albumName)}/track/${encodeURIComponent(t.title)}`,
{ replace: true }
);
}
}}
className={`w-full group flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${
isCurrent ? 'bg-primary text-white' : 'hover:bg-gray-50' isCurrent ? 'bg-primary text-white' : 'hover:bg-gray-50'
}`} }`}
> >
@ -259,7 +280,7 @@ function PCTrackDetail() {
<span className={`text-xs tabular-nums ${isCurrent ? 'text-white/70' : 'text-gray-400'}`}> <span className={`text-xs tabular-nums ${isCurrent ? 'text-white/70' : 'text-gray-400'}`}>
{t.duration || ''} {t.duration || ''}
</span> </span>
</Link> </button>
); );
})} })}
</div> </div>