Advertisement
Gimlao

Analog Clock

Mar 1st, 2025 (edited)
187
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 7.34 KB | Source Code | 0 0
  1. import { useState, useEffect, useRef } from "react";
  2.  
  3. export default function AnalogClock() {
  4.   // Thème par défaut
  5.   const prefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
  6.   const [isDarkMode, setIsDarkMode] = useState(prefersDarkMode);
  7.  
  8.   // Date courante mise à jour chaque seconde
  9.   const [time, setTime] = useState(() => new Date());
  10.  
  11.   // Angle de la trotteuse, géré manuellement
  12.   const [secondsAngle, setSecondsAngle] = useState(6 * time.getSeconds());
  13.  
  14.   // Pour activer/désactiver la transition CSS de la trotteuse
  15.   const [skipTransition, setSkipTransition] = useState(false);
  16.  
  17.   // On garde l’ancien angle pour détecter le saut 59s → 0s
  18.   const oldAngleRef = useRef(secondsAngle);
  19.  
  20.   useEffect(() => {
  21.     // Mise à jour chaque seconde
  22.     const interval = setInterval(() => {
  23.       setTime(new Date());
  24.     }, 1000);
  25.     return () => clearInterval(interval);
  26.   }, []);
  27.  
  28.   useEffect(() => {
  29.     const newSeconds = time.getSeconds();
  30.     const newAngle = 6 * newSeconds; // 6° par seconde (0 → 354)
  31.     const oldAngle = oldAngleRef.current;
  32.  
  33.     // --- Gestion du passage 59 → 0 pour éviter la rotation inverse ---
  34.     if (newAngle < oldAngle - 300) {
  35.       // 1) On autorise la transition pour terminer le mouvement 354° → 360° en 500ms
  36.       setSkipTransition(false);
  37.       setSecondsAngle(360);
  38.  
  39.       // 2) Après 500ms, on passe instantanément à 0° sans transition
  40.       setTimeout(() => {
  41.         setSkipTransition(true);
  42.         setSecondsAngle(0);
  43.       }, 500);
  44.     } else {
  45.       // --- Cas normal : l’aiguille s’anime sur 500ms, puis reste figée 500ms ---
  46.       // Phase d’animation (0 → 500ms)
  47.       setSkipTransition(false);
  48.       setSecondsAngle(newAngle);
  49.  
  50.       // Phase sans transition (500 → 1000ms)
  51.       setTimeout(() => {
  52.         setSkipTransition(true);
  53.       }, 500);
  54.     }
  55.  
  56.     oldAngleRef.current = newAngle;
  57.   }, [time]);
  58.  
  59.   // --- Aiguilles heures + minutes ---
  60.   const rawHours = time.getHours() % 12;
  61.   const rawMinutes = time.getMinutes();
  62.   const rawSeconds = time.getSeconds();
  63.  
  64.   // Mouvement fluide (optionnel) :
  65.   const minutePrecise = rawMinutes + rawSeconds / 60;
  66.   const hourPrecise = rawHours + minutePrecise / 60;
  67.  
  68.   const hoursRotation = 30 * hourPrecise;
  69.   const minutesRotation = 6 * minutePrecise;
  70.  
  71.   // Format 24h
  72.   const formattedTime = time.toLocaleTimeString("fr-FR", { hour12: false });
  73.   const transitionClass = "transition-colors duration-500 ease-in-out";
  74.  
  75.   return (
  76.     <div
  77.       className={`flex flex-col justify-center items-center h-screen ${transitionClass} ${
  78.         isDarkMode ? "bg-gray-900 text-white" : "bg-gray-200 text-black"
  79.       }`}
  80.     >
  81.       {/* Cadran */}
  82.       <div
  83.         className={`relative w-64 h-64 rounded-full border-none flex justify-center items-center transition-all duration-500 ${
  84.           isDarkMode
  85.             ? "bg-gray-800 shadow-[inset_-8px_-8px_15px_rgba(255,255,255,0.15),inset_8px_8px_15px_rgba(0,0,0,0.7)]"
  86.             : "bg-gray-100 shadow-[inset_-8px_-8px_15px_rgba(255,255,255,0.7),inset_8px_8px_15px_rgba(0,0,0,0.2)]"
  87.         }`}
  88.       >
  89.         {/* Marquages des heures */}
  90.         {Array.from({ length: 12 }).map((_, i) => (
  91.           <div
  92.             key={i}
  93.             className={`absolute w-1 h-6 ${transitionClass} ${
  94.               isDarkMode ? "bg-gray-300" : "bg-black"
  95.             }`}
  96.             style={{ transform: `rotate(${i * 30}deg) translateY(-110px)` }}
  97.           />
  98.         ))}
  99.         {/* Marquages des minutes */}
  100.         {Array.from({ length: 60 }).map((_, i) => (
  101.           <div
  102.             key={i}
  103.             className={`absolute w-0.5 h-2 ${transitionClass} ${
  104.               isDarkMode ? "bg-gray-500" : "bg-gray-400"
  105.             }`}
  106.             style={{
  107.               transform: `rotate(${i * 6}deg) translateY(-110px)`,
  108.               opacity: i % 5 === 0 ? 0 : 1,
  109.             }}
  110.           />
  111.         ))}
  112.         {/* Chiffres */}
  113.         {Array.from({ length: 12 }).map((_, i) => (
  114.           <div
  115.             key={i}
  116.             className={`absolute text-xl font-bold ${transitionClass} ${
  117.               isDarkMode ? "text-gray-300" : "text-black"
  118.             }`}
  119.             style={{
  120.               transform: `rotate(${i * 30}deg) translateY(-85px) rotate(-${i * 30}deg)`,
  121.             }}
  122.           >
  123.             {i === 0 ? 12 : i}
  124.           </div>
  125.         ))}
  126.  
  127.         {/* Aiguille des heures (fondu sur la couleur + rotation) */}
  128.         <div
  129.           className={`
  130.             absolute
  131.             w-1.5
  132.             rounded-full
  133.             opacity-75
  134.             ease-in-out
  135.             ${isDarkMode ? "bg-gray-300" : "bg-black"}
  136.           `}
  137.           style={{
  138.             height: "30%",
  139.             top: "20%",
  140.             left: "50%",
  141.             transformOrigin: "bottom",
  142.             transform: `translateX(-50%) rotate(${hoursRotation}deg)`,
  143.             transition: "transform 0.5s ease-in-out, background-color 0.5s ease-in-out",
  144.           }}
  145.         />
  146.  
  147.         {/* Aiguille des minutes (fondu sur la couleur + rotation) */}
  148.         <div
  149.           className={`
  150.             absolute
  151.             w-1.5
  152.             rounded-full
  153.             opacity-75
  154.             ease-in-out
  155.             ${isDarkMode ? "bg-gray-300" : "bg-black"}
  156.           `}
  157.           style={{
  158.             height: "45%",
  159.             top: "5%",
  160.             left: "50%",
  161.             transformOrigin: "bottom",
  162.             transform: `translateX(-50%) rotate(${minutesRotation}deg)`,
  163.             transition: "transform 0.5s ease-in-out, background-color 0.5s ease-in-out",
  164.           }}
  165.         />
  166.  
  167.         {/* Aiguille des secondes (anime 0→500ms, figée 500→1000ms) */}
  168.         <div
  169.           className={`
  170.             absolute
  171.             w-0.5
  172.             rounded-full
  173.             shadow-[4px_4px_10px_rgba(255,165,0,0.5),-4px_-4px_10px_rgba(255,69,0,0.5)]
  174.             opacity-75
  175.             ${isDarkMode ? "bg-orange-400" : "bg-orange-500"}
  176.           `}
  177.           style={{
  178.             height: "50%",
  179.             top: "0%",
  180.             left: "50%",
  181.             transformOrigin: "bottom",
  182.             transform: `translateX(-50%) rotate(${secondsAngle}deg)`,
  183.             transition: skipTransition
  184.               ? "none"
  185.               : "transform 0.5s ease-in-out, background-color 0.5s ease-in-out",
  186.           }}
  187.         />
  188.  
  189.         {/* Centre */}
  190.         <div
  191.           className={`
  192.             absolute
  193.             w-5
  194.             h-5
  195.             border-2
  196.             rounded-full
  197.             shadow-inner
  198.             opacity-100
  199.             ease-in-out
  200.             ${isDarkMode ? "bg-orange-500 border-gray-300" : "bg-orange-500 border-black"}
  201.           `}
  202.           style={{
  203.             transition: "all 0.5s ease-in-out",
  204.           }}
  205.         />
  206.       </div>
  207.  
  208.       {/* Horloge numérique */}
  209.       <div className="mt-6 text-2xl font-mono font-semibold">{formattedTime}</div>
  210.  
  211.       {/* Bouton Jour/Nuit */}
  212.       <button
  213.         className={`
  214.           mt-8
  215.           px-4
  216.           py-2
  217.           bg-gray-700
  218.           text-white
  219.           rounded-lg
  220.           shadow-md
  221.           hover:bg-gray-600
  222.           ${transitionClass}
  223.         `}
  224.         onClick={() => setIsDarkMode(!isDarkMode)}
  225.       >
  226.         {isDarkMode ? "🌞 Mode Jour" : "🌙 Mode Nuit"}
  227.       </button>
  228.     </div>
  229.   );
  230. }
  231.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement