Horloge CSS Javascript
2022-10-09 - No comments
J'ai voulu faire un projet simple faisable en une après-midi pluvieuse.
Ça fait quelques temps que je me disais que je ne savais pas vraiment faire de design web, et que je me contentais de chercher dans Stackoverflow pour des choses assez simples.
Partons sur une copie de la célèbre horloge des SBB-CFF avec les caractéristiques suivantes :
- Cadran blanc avec 12 graduations épaisses pour les heures et 48 graduations fines pour les minutes
- Aiguille des heures qui tourne de 0.5° par minute, de façon discrète
- Aiguille des minutes qui tourne de 6° par minute, de façon discrète
- Aiguille des secondes qui tourne de 6° par seconde, de façon discrète
- Bulbe ajouté à l'extrémité de l'aiguille des secondes, pour la distinguer facilement
Les vraies horloges fonctionnent différemment avec l'aiguille des secondes entrainée par un moteur synchrone, qui fait un tour en 58.5s, puis s'arrête 1.5s sur la graduation 0, avant qu'une impulsion venant d'une horloge maître fasse avancer l'aiguille des minutes de 6°, l'aiguille des heures de 0.5°, et fasse continuer l'aiguille des secondes. Cette fonctionnalité n'a pas été reprise.
Ici, tout est fait de façon vectorielle, sans utiliser d'image.
Structure
Commençons par la structure de la page en séparant le code CSS, HTML et Javascript :
<style> /* Code CSS permettant la mise en page des blocs définis plus bas */ </style>
<body> <div class="clock"> <!-- Code HTML définissant tous les blocs utilisés pour afficher une horloge --> </div> </body>
<script> // Code Javascript permettant d'animer les aiguilles de l'horloge </script>
Hiérarchie
On va devoir définir tous les éléments de façon hiérarchique, par classe :
Clock |- Circle - Quarter*4 - Hour*3 - Minute*4 |- HandsCe qui nous donne 4 quartiers, 12 heures et 48 minutes. Les 12 minutes manquantes sont multiples de 5 et confondues avec les graduations des heures.
Code HTML
C'est assez simple, on va définir les éléments choisis dans la hiérarchie, avec pas mal de copier-coller.
<div class="clock"> <div class="circle"> <div class="quarter1"> <div class="hour12"> <div class="minute1"></div> <div class="minute2"></div> <div class="minute3"></div> <div class="minute4"></div> </div> <div class="hour1"> <div class="minute1"></div> <div class="minute2"></div> <div class="minute3"></div> <div class="minute4"></div> </div> <div class="hour2"> <!-- ... --> </div> </div> <div class="quarter2"> <!-- ... --> </div> <div class="quarter3"> <!-- ... --> </div> <div class="quarter4"> <!-- ... --> </div> <div class="hourshand"></div> <div class="minuteshand"></div> <div class="secondshand"></div> <div class="secondsbulb"></div> </div>Le code a été tronqué pour le rendre lisible.
Ce code seul n'affiche rien. les balises "div" définissent un bloc vide, mais permettent de lui assigner une classe ou un identifiant. On utilise des classes, mais on pourrait utiliser des identifiants si les blocs étaient uniques.
Code CSS
On va tenter de rendre les choses paramétriques en utilisant des variables dans le code CSS:
:root { --size: 256px; /* circle diameter */ --hwidth: calc(var(--size) / 25); /* hours marks 10px*/ --hheight: calc(var(--size) / 8); /* hours marks 32px*/ --mwidth: calc(var(--size) / 85); /* minutes marks 3px*/ --mheight: calc(var(--size) / 32); /* minutes marks 8px */ --hhwidth: calc(var(--size) / 16); /* hours hand 16px */ --hmwidth: calc(3 * var(--size) / 64); /* minutes hand 12px */ --hswidth: calc(var(--size) / 64); /* seconds hand 4px */ --bradius: calc(3 * var(--size) / 32); /* bulb radius 24px */ }Cadran
Puis on va coommencer par définir un disque blanc sur lequel on va afficher tous les éléments du cadran et les aiguilles :
.circle { display: block; margin-left: auto; margin-right: auto; /*position: absolute;*/ top: 0%; left: 0%; background-color: white; width: var(--size); height: var(--size); border-radius: 50%; }Graduations
L'intérêt des classes est de pouvoir appliquer les mêmes paramètres à plusieurs éléments en fonction de leur hiérarchie.
.hour1, .hour2, .hour12 { position: absolute; top: calc(var(--hwidth) / 2); left: calc(50% - var(--hwidth) / 2); background-color: black; width: var(--hwidth); height: var(--hheight); transform-origin: calc(var(--hwidth) / 2) calc( (var(--size) - var(--hwidth) ) / 2); }
Ça donne 3 blocs noirs superposés en haut et au centre du disque. Rien d'impressionnant, sauf qu'on peut faire mieux :
.hour1 {transform: rotate(30deg);}
.hour2 {transform: rotate(60deg);}
Ce qui va laisser un bloc à 12h, et en bouger un à 1h et l'autre à 2h.
L'attribu "transform-origin" permet de spécifier que la transformation se fait à partir du centre de l'horloge, en prenant en compte la largeur des figures.
On fait exactement pareil avec les minutes :
.minute1, .minute2, .minute3, .minute4 { position: absolute; top: 0; left: 0; background-color: black; width: var(--mwidth); height: var(--mheight); transform-origin: calc(var(--mwidth)) calc( var(--size) / 2 - 2 * var(--mwidth)) 122px; } .minute1 {transform: rotate(8deg);} .minute2 {transform: rotate(14deg);} .minute3 {transform: rotate(20deg);} .minute4 {transform: rotate(26deg);}Et comme chaque bloc de 4 graduations de minutes est inclu dans une graduation d'heures, il est aussi dupliqué.
Une fois qu'on a un quartier, on le copie :
.quarter1, .quarter2, .quarter3, .quarter4 {transform-origin: 50% calc(var(--size) / 2);} .quarter1 {transform: rotate(360deg);} .quarter2 {transform: rotate(90deg);} .quarter3 {transform: rotate(180deg);} .quarter4 {transform: rotate(270deg);}
À partir de là, on a un cadran complet affichable.
Aiguilles
On va faire des blocs similaires aux graduations. On va aussi les faire tourner autour du centre, mais on va aussi les placer au centre :
.hourshand { display: block; position: absolute; top: calc(3 * var(--size) / 16); left: calc(50% - var(--hhwidth) / 2); width: var(--hhwidth); height: calc(var(--size) / 2 - var(--hhwidth)); background-color: black; transform-origin: calc(var(--hhwidth) / 2) calc(var(--size) / 2 - 3 * var(--size) / 16); } .minuteshand { display: block; position: absolute; top: var(--hmwidth); left: calc(50% - var(--hmwidth) / 2); width: var(--hmwidth); height: calc( (9/16) * var(--size)); background-color: black; transform-origin: calc(var(--hmwidth) / 2) calc(var(--size) / 2 - var(--hmwidth)); } .secondshand { display: block; position: absolute; top: calc(var(--size) / 8); left: calc(50% - var(--hswidth) / 2); width: var(--hswidth); height: calc(var(--size) / 2); background-color: #eb0000; transform-origin: calc(var(--hswidth) / 2) calc(var(--size) / 2 - var(--size) / 8); } .secondsbulb { display: block; position: absolute; top: var(--bradius); left: calc(50% - var(--bradius) / 2); width: var(--bradius); height: var(--bradius); background-color: #eb0000; border-radius: 50%; transform-origin: calc(var(--bradius) / 2) calc(var(--size) / 2 - var(--bradius)); }À cette étape-ici, on a une horloge complète, mais immobile.
Code Javascript
On commence par définir une fonction qu'on va appeller à chaque seconde :
clock() function clock() { // todo } setInterval(clock, 1000);Ensuite, on va remplir cette fonction pour obtenir les valeurs correspondant à l'heure, minute et seconde actuelle :
const date = new Date(); const seconds = date.getSeconds(); const minutes = date.getMinutes(); //const hours = ((date.getHours() + 11) % 12 + 1); // use integers const hours = ((date.getHours() + 11) % 12 + 1) + minutes / 60.0; // use floats
L'heure est convertie de 24 à 12 heures, mais c'est facultatif, 11h et 23h ont le même angle à 360° près.
Il est aussi possible de choisir entre des heures discrètes, s'incrémentant par pas de 30 degrés toutes les heures, ou par pas de 0.5°, à chaque minute.
On va ensuite convertir ces valeurs en angles :
const second = seconds * 6; const minute = minutes * 6; const hour = hours * 30;Puis faire tourner les aiguilles d'un angle correspondant à la date :
document.querySelector('.secondshand').style.transform = `rotate(${second}deg)` document.querySelector('.secondsbulb').style.transform = `rotate(${second}deg)` document.querySelector('.minuteshand').style.transform = `rotate(${minute}deg)` document.querySelector('.hourshand').style.transform = `rotate(${hour}deg)`
Toutes ces étapes nous donnent une horloge fonctionnelle.