Files
Horloge-Suisse/horloge.js
T
2026-08-21 11:03:00 +02:00

335 lines
9.5 KiB
JavaScript

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());
}
}