335 lines
9.5 KiB
JavaScript
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());
|
||
|
|
}
|
||
|
|
}
|