class SwissClock { constructor(elementId, useUTC = false) { this.element = document.getElementById(elementId); this.useUTC = useUTC; if (!this.element) { console.error(`SwissClock : élément "${elementId}" introuvable.`); return; } this.lastSecond = -1; this.createClock(); this.update(); } // ============================================================ // CRÉATION DE L'HORLOGE // ============================================================ createClock() { this.element.innerHTML = ""; this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); this.svg.setAttribute("viewBox", "0 0 100 100"); this.svg.setAttribute("width", "100%"); this.svg.setAttribute("height", "100%"); this.svg.setAttribute("preserveAspectRatio", "xMidYMid meet"); this.svg.style.display = "block"; // ========================================================== // CADRAN BLANC // ========================================================== const face = document.createElementNS( "http://www.w3.org/2000/svg", "circle", ); face.setAttribute("cx", "50"); face.setAttribute("cy", "50"); face.setAttribute("r", "48"); face.setAttribute("fill", "#ffffff"); this.svg.appendChild(face); // ========================================================== // GRADUATIONS // ========================================================== for (let i = 0; i < 60; i++) { const angle = ((i * 6 - 90) * Math.PI) / 180; const isHour = i % 5 === 0; let outerRadius; let innerRadius; let width; if (isHour) { outerRadius = 45.2; innerRadius = 37.0; width = 2.4; } else { outerRadius = 45.2; innerRadius = 42.0; width = 0.9; } const x1 = 50 + Math.cos(angle) * innerRadius; const y1 = 50 + Math.sin(angle) * innerRadius; const x2 = 50 + Math.cos(angle) * outerRadius; const y2 = 50 + Math.sin(angle) * outerRadius; const tick = document.createElementNS( "http://www.w3.org/2000/svg", "line", ); tick.setAttribute("x1", x1); tick.setAttribute("y1", y1); tick.setAttribute("x2", x2); tick.setAttribute("y2", y2); tick.setAttribute("stroke", "#000000"); tick.setAttribute("stroke-width", width); tick.setAttribute("stroke-linecap", "butt"); this.svg.appendChild(tick); } // ========================================================== // AIGUILLE DES HEURES // ========================================================== this.hourHand = document.createElementNS( "http://www.w3.org/2000/svg", "path", ); this.hourHand.setAttribute( "d", "M 47.3 51.5 L 48.2 28 L 50 24 L 51.8 28 L 52.7 51.5 Z", ); this.hourHand.setAttribute("fill", "#000000"); this.svg.appendChild(this.hourHand); // ========================================================== // AIGUILLE DES MINUTES // ========================================================== this.minuteHand = document.createElementNS( "http://www.w3.org/2000/svg", "path", ); this.minuteHand.setAttribute( "d", "M 48.2 52 L 49.1 15 L 50 11 L 50.9 15 L 51.8 52 Z", ); this.minuteHand.setAttribute("fill", "#000000"); this.svg.appendChild(this.minuteHand); // ========================================================== // TROTTEUSE ROUGE // ========================================================== this.secondHand = document.createElementNS( "http://www.w3.org/2000/svg", "line", ); this.secondHand.setAttribute("x1", "50"); this.secondHand.setAttribute("y1", "55"); this.secondHand.setAttribute("x2", "50"); this.secondHand.setAttribute("y2", "8"); this.secondHand.setAttribute("stroke", "#e30613"); this.secondHand.setAttribute("stroke-width", "1.25"); this.secondHand.setAttribute("stroke-linecap", "round"); this.svg.appendChild(this.secondHand); // ========================================================== // CONTREPOIDS DE LA TROTTEUSE // ========================================================== this.secondCounterweight = document.createElementNS( "http://www.w3.org/2000/svg", "path", ); this.secondCounterweight.setAttribute("d", "M 49.1 52 L 50 61 L 50.9 52 Z"); this.secondCounterweight.setAttribute("fill", "#e30613"); this.svg.appendChild(this.secondCounterweight); // ========================================================== // BOULE ROUGE // ========================================================== this.secondBall = document.createElementNS( "http://www.w3.org/2000/svg", "circle", ); this.secondBall.setAttribute("cx", "50"); this.secondBall.setAttribute("cy", "8"); this.secondBall.setAttribute("r", "2.7"); this.secondBall.setAttribute("fill", "#e30613"); this.svg.appendChild(this.secondBall); // ========================================================== // MOYEU CENTRAL // ========================================================== this.center = document.createElementNS( "http://www.w3.org/2000/svg", "circle", ); this.center.setAttribute("cx", "50"); this.center.setAttribute("cy", "50"); this.center.setAttribute("r", "2.2"); this.center.setAttribute("fill", "#000000"); this.svg.appendChild(this.center); // Petit axe central rouge this.centerRed = document.createElementNS( "http://www.w3.org/2000/svg", "circle", ); this.centerRed.setAttribute("cx", "50"); this.centerRed.setAttribute("cy", "50"); this.centerRed.setAttribute("r", "0.9"); this.centerRed.setAttribute("fill", "#e30613"); this.svg.appendChild(this.centerRed); // ========================================================== // BORDURE EXTÉRIEURE // ========================================================== const border = document.createElementNS( "http://www.w3.org/2000/svg", "circle", ); border.setAttribute("cx", "50"); border.setAttribute("cy", "50"); border.setAttribute("r", "48"); border.setAttribute("fill", "none"); border.setAttribute("stroke", "#000000"); border.setAttribute("stroke-width", "1"); this.svg.appendChild(border); // ========================================================== // AJOUT AU DOM // ========================================================== this.element.appendChild(this.svg); } // ============================================================ // RÉCUPÉRATION DE L'HEURE // ============================================================ getTime() { const now = new Date(); if (this.useUTC) { return { hours: now.getUTCHours(), minutes: now.getUTCMinutes(), seconds: now.getUTCSeconds(), milliseconds: now.getUTCMilliseconds(), }; } return { hours: now.getHours(), minutes: now.getMinutes(), seconds: now.getSeconds(), milliseconds: now.getMilliseconds(), }; } // ============================================================ // MISE À JOUR // ============================================================ update() { const time = this.getTime(); // ---------------------------------------------------------- // AIGUILLE DES HEURES // ---------------------------------------------------------- const hourAngle = (time.hours % 12) * 30 + time.minutes * 0.5; // ---------------------------------------------------------- // AIGUILLE DES MINUTES // ---------------------------------------------------------- const minuteAngle = time.minutes * 6 + time.seconds * 0.1; // ---------------------------------------------------------- // TROTTEUSE // // Mouvement continu très légèrement ralenti à l'approche // de l'index suivant. // ---------------------------------------------------------- const secondProgress = time.seconds + time.milliseconds / 1000; const secondAngle = secondProgress * 6; // ---------------------------------------------------------- // ROTATION HEURES // ---------------------------------------------------------- this.hourHand.setAttribute("transform", `rotate(${hourAngle} 50 50)`); // ---------------------------------------------------------- // ROTATION MINUTES // ---------------------------------------------------------- this.minuteHand.setAttribute("transform", `rotate(${minuteAngle} 50 50)`); // ---------------------------------------------------------- // ROTATION TROTTEUSE // ---------------------------------------------------------- this.secondHand.setAttribute("transform", `rotate(${secondAngle} 50 50)`); this.secondCounterweight.setAttribute( "transform", `rotate(${secondAngle} 50 50)`, ); this.secondBall.setAttribute("transform", `rotate(${secondAngle} 50 50)`); // ---------------------------------------------------------- // MOYEU CENTRAL // Toujours au-dessus des aiguilles // ---------------------------------------------------------- this.center.parentNode.appendChild(this.center); this.center.parentNode.appendChild(this.centerRed); // ---------------------------------------------------------- // PROCHAINE FRAME // ---------------------------------------------------------- requestAnimationFrame(() => this.update()); } }