피드로 돌아가기
Dev.toFrontend
원문 읽기
Building a Reliable Browser Stopwatch Is Harder Than You Think
브라우저 기반 스톱워치 구현 시 setInterval의 부정확성으로 인해 60초 타이밍이 실제로는 63초까지 드리프트되는 문제를 performance.now()와 requestAnimationFrame으로 해결
AI 요약
Context
JavaScript의 setInterval은 최소 지연만 보장하고 정확한 간격을 보장하지 않으므로, 브라우저 이벤트 루프, 탭 포커스 상태, CPU 부하, 가비지 컬렉션 일시 중지에 따라 실제 간격이 10ms 지정 시 14-20ms까지 벌어진다. 60초 타이밍에서 누적 드리프트로 인해 실제로는 63초 이상이 경과해도 60초로 표시되는 오차가 발생한다.
Technical Solution
- setInterval 기반 카운팅을 벽시계 타임스탬프 방식으로 변경: performance.now()로 시작 시점을 기록하고, 매 틱마다 경과시간을 현재 타임스탬프에서 시작 타임스탬프를 빼서 계산
- requestAnimationFrame을 setInterval 대신 사용: 화면 새로고침 속도(일반적으로 60Hz)에 맞춰 표시 업데이트를 동기화하고, DOM을 10ms마다 직접 업데이트하지 않아 효율성 증가
- visibilitychange 이벤트 리스너로 백그라운드 탭 재진입 시 처리: 탭이 다시 활성화되면 원본 시작 타임스탬프로부터 경과시간을 재계산하여 즉시 따라잡기
- pause/resume 시 pauseOffset 변수로 일시 중지된 구간 제외: 일시 중지 시작 타임스탬프와 재개 타임스탬프의 차이를 누적하고, getElapsed()에서 이를 제외하여 일시 중지 시간을 타이밍에서 제거
- 랩 타이밍 기록 시 경과시간과 이전 랩 델타 저장: performance.now() - startTime으로 현재 경과시간을 기록하고, 같은 배열에서 이전 값을 조회하여 개별 랩 시간 계산
Impact
아티클에 정량적 수치가 명시되지 않았습니다.
Key Takeaway
정밀 타이밍이 필요한 브라우저 환경에서는 이벤트 기반 카운팅 대신 벽시계 타임스탬프 기반 계산을 사용해야 하며, requestAnimationFrame과 성능 API를 조합하면 시스템 상태 변화(탭 포커스, 가비지 컬렉션)의 영향으로부터 타이밍 정확도를 보호할 수 있다.
실천 포인트
브라우저에서 시간 측정이 필요한 개발 도구(성능 프로파일러, 테스트 러너, 사용성 테스트 타이머)를 구현할 때는 setInterval 기반 카운팅을 피하고, performance.now() - startTime 방식으로 경과시간을 계산하며, requestAnimationFrame으로 업데이트를 화면 새로고침률에 동기화하면 누적 드리프트 없이 정확한 타이밍을 유지할 수 있다.