CodeTutHub xin giới thiệu đến bạn một dự án nhỏ nhưng rất thú vị: tạo một đồng hồ analog hiển thị thời gian thực, kết hợp cả đồng hồ kim (canvas) và đồng hồ số (digital), được thiết kế thẩm mỹ với TailwindCSS và khả năng responsive theo kích thước màn hình.

✅ Mục tiêu: Hiển thị đồng hồ analog dạng tròn với các kim giờ, phút, giây chuyển động mượt mà, có logo “CodeTutHub” và đồng hồ số bên dưới.

🎯 Kết quả mong đợi

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>CodeTutHub - Analog Clock</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      background-color: #f0f4f8;
      font-family: 'Inter', sans-serif;
      margin: 0;
      overflow: hidden;
    }
    canvas {
      background-color: #ffffff;
      border: 2px solid #333;
      border-radius: 50%;
      box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
      display: block;
      margin: 20px auto;
      max-width: 90vw;
      max-height: 90vh;
    }
    #digital-display {
      font-size: 2.5rem;
      font-weight: bold;
      color: #333;
      margin-top: 30px;
      text-align: center;
    }
  </style>
</head>
<body>
  <div class="flex flex-col items-center p-4">
    <canvas id="analogClockCanvas"></canvas>
    <div id="digital-display" class="rounded-lg p-3 bg-gray-100 shadow-inner"></div>
  </div>

  <script>
    const canvas = document.getElementById('analogClockCanvas');
    const ctx = canvas.getContext('2d');
    const digitalDisplay = document.getElementById('digital-display');

    let size = Math.min(window.innerWidth * 0.7, window.innerHeight * 0.7, 400);
    canvas.width = size;
    canvas.height = size;

    let centerX = canvas.width / 2;
    let centerY = canvas.height / 2;
    let radius = (canvas.width / 2) * 0.9;

    function drawClockFace() {
      ctx.beginPath();
      ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
      ctx.strokeStyle = '#333';
      ctx.lineWidth = 2;
      ctx.stroke();
      ctx.closePath();

      ctx.beginPath();
      ctx.arc(centerX, centerY, radius * 0.03, 0, Math.PI * 2);
      ctx.fillStyle = '#333';
      ctx.fill();
      ctx.closePath();

      ctx.font = `${radius * 0.15}px 'Inter', sans-serif`;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillStyle = '#333';

      for (let i = 1; i <= 12; i++) {
        const angle = (i * Math.PI / 6) - (Math.PI / 2);
        const x = centerX + radius * 0.85 * Math.cos(angle);
        const y = centerY + radius * 0.85 * Math.sin(angle);
        ctx.fillText(i.toString(), x, y);
      }

      drawLogo();
    }

    function drawLogo() {
      ctx.font = `${radius * 0.13}px 'Inter', sans-serif`;
      ctx.fillStyle = '#1e293b'; // Tailwind's slate-800
      ctx.textAlign = 'center';
      ctx.fillText('CodeTutHub', centerX, centerY + radius * 0.5);
    }

    function drawHand(angle, length, width, color) {
      ctx.beginPath();
      ctx.moveTo(centerX, centerY);
      const x = centerX + length * radius * Math.cos(angle);
      const y = centerY + length * radius * Math.sin(angle);
      ctx.lineTo(x, y);
      ctx.strokeStyle = color;
      ctx.lineWidth = width;
      ctx.lineCap = 'round';
      ctx.stroke();
      ctx.closePath();
    }

    function updateClock() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      drawClockFace();

      const now = new Date();
      let hours = now.getHours();
      const minutes = now.getMinutes();
      const seconds = now.getSeconds();

      const hourAngle = ((hours % 12) * Math.PI / 6) + (minutes * Math.PI / (6 * 60)) + (seconds * Math.PI / (360 * 60)) - (Math.PI / 2);
      const minuteAngle = (minutes * Math.PI / 30) + (seconds * Math.PI / (30 * 60)) - (Math.PI / 2);
      const secondAngle = (seconds * Math.PI / 30) - (Math.PI / 2);

      drawHand(hourAngle, 0.5, 6, '#333');
      drawHand(minuteAngle, 0.75, 4, '#333');
      drawHand(secondAngle, 0.8, 2, 'red');

      const formattedHours = hours.toString().padStart(2, '0');
      const formattedMinutes = minutes.toString().padStart(2, '0');
      const formattedSeconds = seconds.toString().padStart(2, '0');
      digitalDisplay.textContent = `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;

      requestAnimationFrame(updateClock);
    }

    window.addEventListener('resize', () => {
      size = Math.min(window.innerWidth * 0.7, window.innerHeight * 0.7, 400);
      canvas.width = size;
      canvas.height = size;
      centerX = canvas.width / 2;
      centerY = canvas.height / 2;
      radius = (canvas.width / 2) * 0.9;
      updateClock();
    });

    window.onload = function () {
      updateClock();
    };
  </script>
</body>
</html>

🧱 Bước 1: Khởi tạo File HTML cơ bản

Chúng ta bắt đầu với một file HTML chuẩn có cấu trúc như sau:

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>CodeTutHub - Analog Clock</title>
  <script src="https://cdn.tailwindcss.com"></script>
  ...
</head>
<body>
  ...
</body>
</html>

🎨 Bước 2: Thiết kế giao diện với TailwindCSS

Chúng ta sử dụng Tailwind để căn giữa nội dung và tạo nền sáng. Bên trong thân trang, có:

  • Một thẻ <canvas> để vẽ đồng hồ analog.
  • Một <div> hiển thị thời gian digital.
html
<div class="flex flex-col items-center p-4">
  <canvas id="analogClockCanvas"></canvas>
  <div id="digital-display" class="rounded-lg p-3 bg-gray-100 shadow-inner"></div>
</div>
css
canvas {
  background-color: #ffffff;
  border: 2px solid #333;
  border-radius: 50%;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
  display: block;
  margin: 20px auto;
  max-width: 90vw;
  max-height: 90vh;
}

🧠 Bước 3: Logic JavaScript – Vẽ và cập nhật đồng hồ

✅ Cấu hình ban đầu

js
const canvas = document.getElementById('analogClockCanvas');
const ctx = canvas.getContext('2d');
const digitalDisplay = document.getElementById('digital-display');

let size = Math.min(window.innerWidth * 0.7, window.innerHeight * 0.7, 400);
canvas.width = size;
canvas.height = size;

✅ Vẽ mặt đồng hồ

js
function drawClockFace() {
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
  ctx.strokeStyle = '#333';
  ctx.lineWidth = 2;
  ctx.stroke();
  ctx.closePath();

  // Tâm đồng hồ
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius * 0.03, 0, Math.PI * 2);
  ctx.fillStyle = '#333';
  ctx.fill();
  ctx.closePath();

  // Số giờ
  ctx.font = `${radius * 0.15}px 'Inter', sans-serif`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillStyle = '#333';

  for (let i = 1; i <= 12; i++) {
    const angle = (i * Math.PI / 6) - (Math.PI / 2);
    const x = centerX + radius * 0.85 * Math.cos(angle);
    const y = centerY + radius * 0.85 * Math.sin(angle);
    ctx.fillText(i.toString(), x, y);
  }

  drawLogo();
}

✅ Thêm Logo “CodeTutHub”

js
function drawLogo() {
  ctx.font = `${radius * 0.13}px 'Inter', sans-serif`;
  ctx.fillStyle = '#1e293b'; // slate-800
  ctx.textAlign = 'center';
  ctx.fillText('CodeTutHub', centerX, centerY + radius * 0.5);
}

✅ Vẽ kim đồng hồ

js
function drawHand(angle, length, width, color) {
  ctx.beginPath();
  ctx.moveTo(centerX, centerY);
  const x = centerX + length * radius * Math.cos(angle);
  const y = centerY + length * radius * Math.sin(angle);
  ctx.lineTo(x, y);
  ctx.strokeStyle = color;
  ctx.lineWidth = width;
  ctx.lineCap = 'round';
  ctx.stroke();
  ctx.closePath();
}

🕒 Bước 4: Cập nhật thời gian liên tục

Chúng ta tính toán góc xoay dựa trên Date():

js
function updateClock() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawClockFace();

  const now = new Date();
  let hours = now.getHours();
  const minutes = now.getMinutes();
  const seconds = now.getSeconds();

  const hourAngle = ((hours % 12) * Math.PI / 6) + (minutes * Math.PI / (6 * 60)) + (seconds * Math.PI / (360 * 60)) - (Math.PI / 2);
  const minuteAngle = (minutes * Math.PI / 30) + (seconds * Math.PI / (30 * 60)) - (Math.PI / 2);
  const secondAngle = (seconds * Math.PI / 30) - (Math.PI / 2);

  drawHand(hourAngle, 0.5, 6, '#333');
  drawHand(minuteAngle, 0.75, 4, '#333');
  drawHand(secondAngle, 0.8, 2, 'red');

  const formattedHours = hours.toString().padStart(2, '0');
  const formattedMinutes = minutes.toString().padStart(2, '0');
  const formattedSeconds = seconds.toString().padStart(2, '0');
  digitalDisplay.textContent = `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;

  requestAnimationFrame(updateClock);
}

📱 Bước 5: Hỗ trợ Responsive

js
window.addEventListener('resize', () => {
  size = Math.min(window.innerWidth * 0.7, window.innerHeight * 0.7, 400);
  canvas.width = size;
  canvas.height = size;
  centerX = canvas.width / 2;
  centerY = canvas.height / 2;
  radius = (canvas.width / 2) * 0.9;
  updateClock();
});

window.onload = function () {
  updateClock();
};

📦 Tổng kết

Chỉ với một chút HTML, CSS và JavaScript thuần, bạn đã có thể tạo ra một đồng hồ analog đầy đủ chức năng, hiển thị thời gian thực và tương thích với mọi kích thước màn hình.

💡 Bạn có thể mở rộng thêm các tính năng:

  • Đổi theme sáng/tối
  • Hiển thị múi giờ
  • Tùy chỉnh logo, font, màu sắc...

Hy vọng bạn học được điều gì đó mới từ bài viết này! Nếu thấy hữu ích, hãy chia sẻ bài viết và theo dõi CodeTutHub.com để cập nhật thêm nhiều hướng dẫn lập trình thú vị nhé!