Làm đồng hồ đếm ngược bằng JavaScript: từ setInterval đến xử lý ngày tháng

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Đồng hồ đếm ngược xuất hiện ở khắp nơi: sự kiện khuyến mãi, ngày ra mắt, kỳ thi. Làm một cái bằng JavaScript là bài tập rất hay để hiểu đối tượng Date, hàm hẹn giờ setInterval và cách cập nhật giao diện theo thời gian.

Nguyên lý hoạt động

Ý tưởng đơn giản: mỗi giây ta lấy thời điểm đích trừ đi thời điểm hiện tại để ra số mili giây còn lại, sau đó đổi thành ngày, giờ, phút, giây rồi hiển thị. Điểm quan trọng là luôn tính lại từ đồng hồ hệ thống, không trừ dần mỗi giây. Vì setInterval không đảm bảo chính xác tuyệt đối (và bị trình duyệt làm chậm khi tab ở nền), nếu bạn tự trừ 1 mỗi lần, đồng hồ sẽ bị lệch dần.

Bước 1: HTML

<div class="countdown">
  <div><span id="days">00</span><small>Ngày</small></div>
  <div><span id="hours">00</span><small>Giờ</small></div>
  <div><span id="minutes">00</span><small>Phút</small></div>
  <div><span id="seconds">00</span><small>Giây</small></div>
</div>
<p id="message"></p>

Bước 2: CSS gọn gàng

.countdown { display: flex; gap: 12px; justify-content: center; margin-top: 40px; }
.countdown div { background: #1f2937; color: #fff; padding: 16px 20px; border-radius: 12px; text-align: center; min-width: 80px; }
.countdown span { display: block; font-size: 2.2rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.countdown small { opacity: .7; }
#message { text-align: center; font-size: 1.2rem; margin-top: 20px; }

Thuộc tính font-variant-numeric: tabular-nums làm các chữ số có độ rộng bằng nhau, giúp con số không bị nhảy ngang khi thay đổi.

Bước 3: JavaScript

// Đặt thời điểm đích (năm, tháng bắt đầu từ 0, ngày, giờ, phút, giây)
const target = new Date(2030, 0, 1, 0, 0, 0).getTime();

const els = {
  days: document.getElementById('days'),
  hours: document.getElementById('hours'),
  minutes: document.getElementById('minutes'),
  seconds: document.getElementById('seconds'),
};
const message = document.getElementById('message');

const pad = (n) => String(n).padStart(2, '0');

function update() {
  const diff = target - Date.now();

  if (diff <= 0) {
    clearInterval(timer);
    Object.values(els).forEach((el) => (el.textContent = '00'));
    message.textContent = 'Sự kiện đã bắt đầu!';
    return;
  }

  const totalSeconds = Math.floor(diff / 1000);
  const days = Math.floor(totalSeconds / 86400);
  const hours = Math.floor((totalSeconds % 86400) / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  els.days.textContent = pad(days);
  els.hours.textContent = pad(hours);
  els.minutes.textContent = pad(minutes);
  els.seconds.textContent = pad(seconds);
}

const timer = setInterval(update, 1000);
update();

Hãy chú ý vài chi tiết. Tháng trong new Date(năm, tháng, ...) được đếm từ 0, nên 0 là tháng Giêng. Hàm padStart thêm số 0 phía trước để luôn hiển thị hai chữ số. Ta gọi update() ngay một lần để người dùng không phải chờ một giây mới thấy số. Khi hết giờ, clearInterval dừng bộ hẹn giờ để không chạy vô ích.

Vấn đề múi giờ

Khi dùng new Date(2030, 0, 1), thời điểm được hiểu theo múi giờ của người xem. Nếu sự kiện diễn ra đồng thời cho mọi người (như livestream), hãy dùng chuỗi ISO có múi giờ, ví dụ new Date('2030-01-01T00:00:00+07:00'), để mọi người đếm đến cùng một khoảnh khắc.

Cải tiến

  • Lưu thời điểm đích trong localStorage để đếm cho đợt khuyến mãi riêng của từng người dùng.
  • Cho người dùng chọn ngày bằng thẻ <input type="datetime-local">.
  • Dùng requestAnimationFrame nếu muốn hiệu ứng mượt hơn, hoặc thêm hiệu ứng lật số bằng CSS.
  • Dùng thuộc tính aria-live="polite" cho phần thông báo để hỗ trợ trình đọc màn hình, nhưng tránh thông báo mỗi giây.

Kiểm tra bằng cách thử nhiều tình huống

Một đồng hồ đếm ngược tốt cần được thử ở nhiều tình huống: thời điểm đích đã qua, đích ở rất xa, người dùng chuyển sang tab khác vài phút rồi quay lại, hoặc đổi giờ của máy tính. Vì ta luôn tính lại từ Date.now(), đồng hồ sẽ hiện đúng ngay khi bạn quay lại tab. Hãy thử đặt thời điểm đích chỉ cách hiện tại 10 giây để xem phần thông báo kết thúc hoạt động đúng chưa. Cũng nên nhớ rằng đồng hồ của máy người dùng có thể sai lệch; nếu sự kiện đòi hỏi chính xác tuyệt đối, hãy lấy thời gian từ máy chủ.

Kết luận

Qua một bài tập nhỏ, bạn đã chạm tới bốn kiến thức hay gặp: tính toán thời gian, hẹn giờ, thao tác DOM và xử lý múi giờ. Nguyên tắc "tính lại từ nguồn thời gian chuẩn" còn áp dụng cho nhiều bài toán khác như đồng hồ bấm giờ hay phiên làm bài thi.

Muốn nắm chắc JavaScript qua nhiều dự án thực hành, bạn có thể tham khảo các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment