1. 기본 구조
<div style={{
width: '300px', // 전체 진행바 컨테이너 너비
padding: '20px 10px', // 클릭 영역 확보
position: 'relative',
cursor: 'pointer'
}}>
{/* 진행바 트랙 (배경) */}
<div style={{
width: '100%',
height: '6px',
background: 'rgba(0, 0, 0, 0.1)',
borderRadius: '3px',
position: 'relative'
}}>
{/* 실제 진행 상태 표시 */}
<div style={{
width: `${(currentIndex / (data.length - 1)) * 100}%`,
height: '100%',
background: 'blue',
borderRadius: '3px'
}} />
</div>
</div>
2. 진행률 계산 원리
// 진행률 계산식
const progress = (currentIndex / (data.length - 1)) * 100;
// 예시:
// currentIndex: 5
// data.length: 11
// progress = (5 / 10) * 100 = 50%
3. 클릭 위치 계산과 이동
onClick={(e) => {
const rect = e.currentTarget.getBoundingClientRect();
const barStart = 10; // padding-left 값
const barWidth = rect.width - 20; // 전체 너비 - (padding-left + padding-right)
// 클릭 위치의 상대적 비율 계산
const clickPosition = (e.clientX - rect.left - barStart) / barWidth;
// 새로운 인덱스 계산
const newIndex = Math.floor(clickPosition * (data.length - 1));
// 범위 내 값으로 보정
const boundedIndex = Math.max(0, Math.min(newIndex, data.length - 1));
setCurrentIndex(boundedIndex);
}}
4. 실시간 업데이트
useEffect(() => {
if (!isPlaying || !data?.length) return;
const interval = setInterval(() => {
setCurrentIndex(prev => {
if (prev >= data.length - 1) {
setIsPlaying(false);
return prev;
}
return prev + 1;
});
}, baseInterval / playbackSpeed);
return () => clearInterval(interval);
}, [isPlaying, data, playbackSpeed]);
5. 핵심 동작 원리
- 상대적 위치 계산 :
// 진행바 내 상대적 위치 = (현재값 - 최소값) / (최대값 - 최소값)
const relativePosition = (current - min) / (max - min);
- 픽셀 위치 변환 :
// 픽셀 위치 = 상대적 위치 * 진행바 전체 너비
const pixelPosition = relativePosition * totalWidth;
6. 주요 이벤트 처리 :
// 드래그 시작
const handleDragStart = (e) => {
setIsDragging(true);
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', handleDragEnd);
};
// 드래그 중
const handleDrag = (e) => {
if (!isDragging) return;
const rect = barRef.current.getBoundingClientRect();
const position = (e.clientX - rect.left) / rect.width;
updateProgress(position);
};
// 드래그 종료
const handleDragEnd = () => {
setIsDragging(false);
document.removeEventListener('mousemove', handleDrag);
document.removeEventListener('mouseup', handleDragEnd);
};
7. 성능 최적화 :
// 불필요한 리렌더링 방지
const updateProgress = useCallback((position) => {
const bounded = Math.max(0, Math.min(1, position));
setProgress(bounded);
}, []);
// 메모이제이션된 스타일
const progressStyle = useMemo(() => ({
width: `${progress * 100}%`
}), [progress]);