Analog Clock

  • Typescript
  • React
  • Tailwind CSS
components/analog-clock.tsx
"use client";

import { useEffect, useState } from "react";

const SIZE = 180;

export function AnalogClock() {
  const [clockRotation, setClockRotation] = useState({
    hour: 0,
    minute: 0,
    second: 0,
  });

  const handleClock = () => {
    const currentDate = new Date();
    const currentHour = currentDate.getHours() % 12;
    const currentMinute = currentDate.getMinutes();
    const currentSecond = currentDate.getSeconds();

    setClockRotation({
      hour: currentHour * 30 + currentMinute * 0.5,
      minute: currentMinute * 6 + currentSecond * 0.1,
      second: currentSecond * 6,
    });
  };

  useEffect(() => {
    handleClock();
    const intervalId = setInterval(handleClock, 1000);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return (
    <div
      className="rounded-full bg-gray-50 relative"
      style={{ width: SIZE, height: SIZE }}
    >
      <div>
        <div
          className="bg-accent-orange w-1.5 h-12 absolute origin-[50%_100%]"
          style={{
            rotate: `${+clockRotation.hour}deg`,
            left: SIZE / 2 - 3,
            bottom: SIZE / 2,
          }}
        ></div>
        <div
          className="bg-black w-0.5 h-[72px] absolute origin-bottom"
          style={{
            rotate: `${+clockRotation.minute}deg`,
            left: SIZE / 2 - 1,
            bottom: SIZE / 2,
          }}
        ></div>
        <div
          className="bg-black w-px h-24 absolute origin-[center_calc(100%-16px)] left-1/2"
          style={{
            rotate: `${+clockRotation.second}deg`,
            left: SIZE / 2 - 0.5,
            bottom: SIZE / 2 - 16,
          }}
        ></div>
        <div className="size-2 bg-background border rounded-full absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"></div>
      </div>

      <div>
        {Array.from({ length: 60 }).map((_, i) => (
          <div
            key={i}
            className="w-px h-1 absolute left-1/2 bg-gray-500 -translate-x-1/2 nth-of-type-[5n]:h-2 nth-of-type-[5n]:bg-gray-900"
            style={{
              transform: `rotate(${6 * (i + 1)}deg)`,
              transformOrigin: `50% ${SIZE / 2}px`,
            }}
          ></div>
        ))}
      </div>
    </div>
  );
}

<AnalogClock />