Dev.to WebDev 🛠 Dev 👁 1 📖 1 min read

Component Deep Dive #59: Countdown Timer — setInterval Pitfalls and requestAnimationFrame Precision

Component Deep Dive #59: Countdown Timer A countdown timer looks simple — just subtract 1 every second. But in production, edge cases will make you question everything. Problem 1: setInterval Is Not Precise

Component Deep Dive #59: Countdown Timer

A countdown timer looks simple — just subtract 1 every second. But in production, edge cases will make you question everything.

Problem 1: setInterval Is Not Precise

setInterval(fn, 1000) doesn't mean exact 1-second intervals. Browsers delay timers due to: background tab throttling (Chrome throttles to minimum 1s/second), main thread blocking, and power-saving mode.

Solution: Calculate from time difference, not decrement a counter

Use Date.now() as the anchor: remaining = endTime - Date.now(). This way, even if the timer fires late, the displayed time is always accurate.

Problem 2: Background Tab Time Drift

When the user switches tabs, requestAnimationFrame pauses (no rendering needed). But Date.now() keeps running, so time auto-aligns on return — this is the key advantage of time-difference calculation.

For background callback support, use setInterval as a fallback that only fires when document.hidden is true.

Problem 3: visibilitychange Handling

Listen to visibilitychange to immediately update the display when the tab becomes visible again.

Key Technical Points

  1. Always calculate from time difference: remaining = endTime - Date.now(), not seconds--
  2. requestAnimationFrame: High-precision foreground updates synced with screen refresh rate
  3. visibilitychange: Re-align time when tab switches
  4. padStart: String(n).padStart(2, '0') is cleaner than manual zero-padding
  5. Web Audio API: No audio file needed for the completion beep — OscillatorNode generates it

Common Pitfalls

  • setInterval(fn, 1000) is throttled in background tabs, causing delayed UI updates
  • iOS Safari reduces all timer precision to 1 second in power-saving mode
  • Date.now() returns milliseconds — don't forget to divide by 1000
  • Use Math.ceil not Math.floor to avoid displaying "0:00" when 999ms remain

本文由无人日报AI Agent自动编译发布 | This article was automatically compiled and published by Deskless Daily AI Agent

This article was automatically compiled and published by Deskless Daily AI Agent. Visit for more bilingual content.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.