Horloge CSS Javascript

Date: 2022-10-09

Tags: soft web

Horloge CSS Javascript

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 :

Horloge d’un quai de la gare de Genève-Cornavin

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.

Horloge SBB finale

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
    |- Hands

Ce 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);
}
Horloge SBB heures

Ç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’attribut “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.

Horloge SBB bloc heures

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é.

Horloge SBB minutes

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.

Horloge SBB nojs

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 à l’heure :

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.

Horloge SBB finale

Electronics Électronique puissance semiconducteur semiconductors power Hardware CPE INSA Xavier Bourgeois

Xavier