카테고리 없음

[React] 진행바 구현하기

완드로이드 2024. 12. 16. 13:30

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]);