Aller au menu - Aller au contenu

Icône Créer une horloge animée avec CSS3 et Javascript

Mise à jour : 28/09/2010
Difficulté : Facile Facile Creative Commons BY-NC-SA
428 visites depuis 7 jours, classé 258/786
Le but de ce tutoriel sera de vous montrer comment exploiter certaines propriétés CSS3 pour créer une horloge et comment l'animer avec Javascript.
La création de ce petit module nécessite peu de connaissances : il suffit de connaître les bases du Javascript, du CSS et, bien sûr, de l'HTML. :)

L'horloge tel que présentée dans ce tutoriel ne sera pas compatible avec Internet Explorer (du moins, tant qu'il n'implémentera pas les propriétés CSS3). Il y a toutefois des solutions pour remédier à ce problème et des liens sont donnés à la fin du tutoriel.

Voici un aperçu du résultat final :

Image utilisateur

Introduction

Le gros problème de ce module est la compatibilité. En effet, il utilise des propriétés CSS3 qui ne sont actuellement prises en charge que par les navigateurs récents (à l'exception d'Internet Explorer).
Voici la liste des principaux navigateurs qui le supportent :
  • Firefox 3.5 et plus ;
  • Safari 3.0.4 et plus ;
  • Google Chrome ;
  • Opera 10.5 et plus ;

Si vous voulez savoir si votre navigateur peut afficher le module, allez sur cette page. Si votre navigateur n'est pas compatible, il faudra vous procurer un navigateur plus récent.

Nous verrons quand même comment faire pour que tous navigateurs affichent une page correcte sans bug, même s'ils ne sont pas compatibles.

D'après les différentes sources que j'ai pu trouver, il semblerait que la bêta d'IE9 ne supporte pas certaines propriétés CSS3 utilisés dans ce tutoriel. Il y a donc fort à parier qu'il faudra attendre IE10 pour pouvoir faire marcher cette horloge sans utiliser des techniques plus que douteuses.

Le fonctionnement de l'horloge

Dans cette partie, je vais expliquer le principe de ce module, comment il fonctionne. On va décortiquer son fonctionnement pour pouvoir coder efficacement et sans tracas. :) On ne codera que dans la partie suivante.

Transform et transform-origin


Le module se base sur les propriétés CSS3 transform et transform-origin. La première propriété transforme (quelle surprise :D ) un élément de la page. Les transformations les plus intéressantes sont la mise à l'échelle, la rotation et la translation. Nous ne nous servirons que de la rotation.
Voici un exemple d'utilisation pour que vous compreniez bien comment cela fonctionne :
Code : HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" >
   <head>
      <title>Créer une horloge animée avec CSS3 et Javascript</title>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <style type="text/css">
        h1
         {
            -moz-transform: rotate(-5deg);
            -webkit-transform: rotate(-5deg);
            -o-transform: rotate(-5deg);
            transform: rotate(-5deg);
            -moz-transform-origin: center center;
            -webkit-transform-origin: center center;
            -o-transform-origin: center center;
            transform-origin: center center;
            text-align: center;
         }
      </style>
   </head>
   <body>
      <h1>Mon super titre</h1>
   </body>
</html>

Le résultat ici.

Eh ! Mais je croyais qu'on utilisait juste les propriétés transform et transform-origin ! o_O

Qu'est ce que c'est que ces -moz-, -webkit- et -o-?


Bonne question. En fait, ces propriétés (transform et transform-origin) ne sont pas encore dans les standards du W3C (la spécification CSS3 n'étant pas encore finalisée), donc les navigateurs qui les utilisent mettent un préfixe (-moz- pour Mozilla, -webkit- pour Safari, -ms- pour Internet Explorer et -o- pour Opera) pour ne pas interférer avec les standards. Il est tout de même préférable de rajouter les propriétés sans suffixe pour qu'elles fonctionnent pleinement le jour où un navigateur ne comprendra que la propriété non suffixé (Merci à SpaceFox pour la remarque).

Ça veut dire qu'on devra mettre -moz-*, -webkit-* et -o-* à chaque fois ?

Malheureusement, oui.

On décortique le code



L'xHTML


Pour la partie xHTML, l'horloge est très simple, vu que ce sont trois images (les trois aiguilles) dans un conteneur (ici un div).

Le CSS


Du côté du CSS, ça reste toujours aussi simple :
On va mettre le cadran comme fond de l'horloge. On va arrondir les bords du div conteneur de l'horloge pour qu'il fasse un cercle entourant le cadran et on va placer l'horloge en position relative pour pouvoir positionner les aiguille en fonction de la position de l'horloge.

Le Javascript


C'est ici que résident tous les secrets de cette horloge.
On va commencer par déterminer le nombre de secondes écoulées depuis le début de la journée.
Ensuite, on va faire tourner les aiguilles pour qu'elles indiquent l'heure du chargement de la page.
Et enfin, toutes les secondes, on va incrémenter le nombre de secondes écoulées depuis le début de la journée, puis replacer les aiguilles en conséquence.


Avouez tout de même que le fonctionnement est vraiment simple ! :)

On code

Nous y voici donc, au codage de cette horloge. Cela va aller assez vite, vu que l'on a déjà fait 70% du travail en décortiquant le fonctionnement de l'horloge.

L'xHTML


On va donc commencer par mettre un div qui contient les 3 aiguilles :
Code : HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" >
<head>
   <title>Horloge</title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
   <div id="horloge">
      <img class="aig" id="aigS" src="aigS.png" alt="aiguille des secondes" />
      <img class="aig" id="aigM" src="aigM.png" alt="aiguille des minutes" />
      <img class="aig" id="aigH" src="aigH.png" alt="aiguille des heures" />
   </div>
</body>
</html>

Voici d'ailleurs les images des trois aiguilles, ainsi que le cadran (vous remarquerez au passage mon talent particulier d'artiste :p ) :


Le CSS


On met l'horloge en position: relative; . Cela permet de pouvoir positionner les aiguilles en fonction de l'emplacement de l'horloge.
On met les aiguilles en position: absolute; .
On met le cadran : background: url('cadran.png'); .
On agrandit l'horloge pour que le cadran soit entier : height: 567px; width: 567px; .
On met le bas des aiguilles au milieu de l'horloge : bottom: 283px; .
On règle l'abscisse des aiguilles : /* aiguille des secondes : */left: 283px; /* pour les deux autres : */left : 278px; .
On place le centre de rotation des aiguilles : transform-origin: bottom center; (je n'ai pas mis les suffixes).
Et enfin, on met une bordure à l'horloge et on l'arrondit pour qu'elle soit circulaire : border: 1px solid black; border-radius: 284px; . On indique ici la valeur de l'arrondi en pixels parce que webkit n'accepte pas la valeur en pourcentage.

On met tout ça dans le code HTML :
Code : HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" >
<head>
   <title>Horloge</title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <style type="text/css">
      .aig
      {
         position: absolute;
         bottom: 283px;
         -moz-transform-origin: bottom center;
         -webkit-transform-origin: bottom center;
         -o-transform-origin: bottom center;
         transform-origin: bottom center;
      }
      #horloge
      {
         position: relative;
         height: 567px;
         width: 567px;
         border: 1px solid black;
         -moz-border-radius: 284px;
         -webkit-border-radius: 284px;
         -o-border-radius: 284px;
         border-radius: 284px;
         background: url('cadran.png');
      }
   </style>
</head>
<body>
   <div id="horloge">
      <img class="aig" id="aigS" src="aigS.png" alt="aiguille des secondes" style="left: 283px;" />
      <img class="aig" id="aigM" src="aigM.png" alt="aiguille des minutes" style="left: 278px;" />
      <img class="aig" id="aigH" src="aigH.png" alt="aiguille des heures" style="left: 278px;" />
   </div>
</body>
</html>


Le Javascript


On détermine le nombre de secondes écoulées depuis le début de la journée :
Code : JavaScript
1
2
var d    = new Date();
var time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();

On place les aiguilles. Je vais expliquer les calculs juste après :
Code : JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var aigS = document.getElementById('aigS');
var aigM = document.getElementById('aigM');
var aigH = document.getElementById('aigH');

aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';

Voici un petit tableau de proportionnalité pour comprendre les calculs :

Nombre de degrésMinutes, secondesHeures
360 60 12
Nombre inconnu
(noté x)
Nombre de minutes ou secondes
(notées respectivement « m » et « s »)
Nombre d'heures
(notées « h »)

Comme vous le voyez, il n'y a qu'un nombre inconnu ( : x ), donc pour calculer l'angle de décalage de l'aiguille des secondes, on doit faire : x_s=\frac{360s}{60}
Pour celle des minutes, on doit faire : x_m=\frac{360m}{60}
Et pour celle des heures, on fait : x_h=\frac{360h}{12}
Et donc en simplifiant, on obtient respectivement : x_s=6s\qquad ;\qquad x_m=6m\qquad;\qquad x_h=30h

Oui, mais on ne possède que le nombre de secondes !

J'y viens, j'y viens. Je devine que tout le monde sait qu'il y a 3600 secondes dans une heure et 60 secondes dans une minute.
Donc on a : m=\frac{s}{60}\qquad;\qquad h=\frac{s}{3600}
Les calculs de tout à l'heure deviennent donc, en simplifiant : x_s=6s\qquad ;\qquad x_m=\frac{s}{10}\qquad;\qquad x_h=\frac{s}{120}

Retournons à notre Javascript :
Il nous reste juste à faire en sorte que l'horloge se « mette à l'heure » (c'est le cas de le dire :p ) toutes les secondes.
Pour cela, on va utiliser setInterval() qui va exécuter une fonction toutes les x millisecondes :
Code : JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
setInterval(function() {
   time++;
   aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
   aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
   aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
   aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
   aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
   aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
   aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
   aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
}, 1000);


On a donc au final ce code Javascript :
Code : JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var d    = new Date();
var time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();

var aigS = document.getElementById('aigS');
var aigM = document.getElementById('aigM');
var aigH = document.getElementById('aigH');

aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';

setInterval(function() {
   time++;
   aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
   aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
   aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
   aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
   aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
   aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
   aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
   aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
}, 1000);


Et inséré dans le code HTML ça donne :
Code : HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" >
<head>
   <title>Horloge</title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <style type="text/css">
      .aig
      {
         position: absolute;
         bottom: 283px;
         -moz-transform-origin: bottom center;
         -webkit-transform-origin: bottom center;
         -o-transform-origin: bottom center;
         transform-origin: bottom center;
      }
      #horloge
      {
         position: relative;
         height: 567px;
         width: 567px;
         border: 1px solid black;
         -moz-border-radius: 284px;
         -webkit-border-radius: 284px;
         -o-border-radius: 284px;
         border-radius: 284px;
         background: url('cadran.png');
      }
   </style>
   <script type="text/javascript">
   window.onload = function() // Il faut pense à attendre que la page soit chargé avant d'exécuter le script
   {
      var d    = new Date();
      var time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();

      var aigS = document.getElementById('aigS');
      var aigM = document.getElementById('aigM');
      var aigH = document.getElementById('aigH');

      aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
      aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
      aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
      aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
      aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
      aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
      aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
      aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';

      setInterval(function() {
         time++;
         aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
         aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
      }, 1000);
   }
   </script>
</head>
<body>
   <div id="horloge">
      <img class="aig" id="aigS" src="aigS.png" alt="aiguille des secondes" style="left: 283px;" />
      <img class="aig" id="aigM" src="aigM.png" alt="aiguille des minutes" style="left: 278px;" />
      <img class="aig" id="aigH" src="aigH.png" alt="aiguille des heures" style="left: 278px;" />
   </div>
</body>
</html>


Vous pouvez voir le résultat ici. Attention, le cadran de fond est assez long à charger la première fois.

Pour aller plus loin

Compatibilité


On a réussi à créer notre horloge, mais elle n'est compatible qu'avec certains navigateurs et de ce fait s'affiche mal dans les autres.
On va commencer par régler ce problème. Tout d'abord, il ne faut pas afficher les aiguilles et le cadran, on va donc les supprimer de l'HTML et de la CSS :
Code : HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" >
<head>
   <title>Horloge</title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <style type="text/css">
      .aig
      {
         position: absolute;
         bottom: 283px;
         -moz-transform-origin: bottom center;
         -webkit-transform-origin: bottom center;
         -o-transform-origin: bottom center;
         transform-origin: bottom center;
      }
      #horloge
      {
         position: relative;
         height: 567px;
         width: 567px;
         border: 1px solid black;
         -moz-border-radius: 284px;
         -webkit-border-radius: 284px;
         -o-border-radius: 284px;
         border-radius: 284px;
      }
   </style>
   <script type="text/javascript">
   window.onload=function()
   {
      var d    = new Date();
      var time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();

      var aigS = document.getElementById('aigS');
      var aigM = document.getElementById('aigM');
      var aigH = document.getElementById('aigH');

      aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
      aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
      aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
      aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
      aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
      aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
      aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
      aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
      aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';

      setInterval(function() {
         time++;
         aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
         aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
      }, 1000);
   }
   </script>
</head>
<body>
   <div id="horloge">
   </div>
</body>
</html>

On va les afficher en Javascript via DOM :
Code : JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var aigS = document.createElement('img');
   aigS.setAttribute('src'  , 'aigS.png');
   aigS.setAttribute('class', 'aig');
   aigS.setAttribute('alt'  , 'aiguille des secondes');
   aigS.setAttribute('style', 'left: 283px;');

var aigM = document.createElement('img');
   aigM.setAttribute('src'  , 'aigM.png');
   aigM.setAttribute('class', 'aig');
   aigM.setAttribute('alt'  , 'aiguille des minutes');
   aigM.setAttribute('style', 'left: 278px;');

var aigH = document.createElement('img');
   aigH.setAttribute('src'  , 'aigH.png');
   aigH.setAttribute('class', 'aig');
   aigH.setAttribute('alt'  , 'aiguille des heures');
   aigH.setAttribute('style', 'left: 278px;');

var horloge = document.getElementById('horloge');
   horloge.appendChild(aigS);
   horloge.appendChild(aigM);
   horloge.appendChild(aigH);
   horloge.setAttribute('style', 'background: url(\'cadran.png\');');


Et maintenant, on va n'exécuter le Javascript que si le navigateur est compatible :
Code : JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
window.onload = function()
   {
      if(document.getElementsByTagName('body')[0].style.MozTransform == ''
         || document.getElementsByTagName('body')[0].style.WebkitTransform == ''
         || document.getElementsByTagName('body')[0].style.OTransform == ''
         || document.getElementsByTagName('body')[0].style.OTransform
         || document.getElementsByTagName('body')[0].style.transform == '')
      {
         var aigS = document.createElement('img');
            aigS.setAttribute('src'  , 'aigS.png');
            aigS.setAttribute('class', 'aig');
            aigS.setAttribute('alt'  , 'aiguille des secondes');
            aigS.setAttribute('style', 'left: 283px;');

         var aigM = document.createElement('img');
            aigM.setAttribute('src'  , 'aigM.png');
            aigM.setAttribute('class', 'aig');
            aigM.setAttribute('alt'  , 'aiguille des minutes');
            aigM.setAttribute('style', 'left: 278px;');

         var aigH = document.createElement('img');
            aigH.setAttribute('src'  , 'aigH.png');
            aigH.setAttribute('class', 'aig');
            aigH.setAttribute('alt'  , 'aiguille des heures');
            aigH.setAttribute('style', 'left: 278px;');

         var horloge = document.getElementById('horloge');
            horloge.appendChild(aigS);
            horloge.appendChild(aigM);
            horloge.appendChild(aigH);
            horloge.setAttribute('style', 'background: url(\'cadran.png\');');

         var d    = new Date();
         var time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();

         aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
         aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
         aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
         aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
         aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';

         setInterval(function() {
            time++;
            aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
            aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
         }, 1000);
      }
   };

Vous remarquerez dans la condition que pour de sombres raisons, suivant la version Opera ne va pas donner une chaine de caractère, mais un objet CSSTransformValue, comportement qui n'est valable que pour cette propriété.

Avec l'HTML, la CSS et le Javascript, on obtient au final :
Code : HTML
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" >
<head>
   <title>Horloge</title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <style type="text/css">
      .aig
      {
         position: absolute;
         bottom: 283px;
         -moz-transform-origin: bottom center;
         -webkit-transform-origin: bottom center;
         -o-transform-origin: bottom center;
         transform-origin: bottom center;
      }
      #horloge
      {
         position: relative;
         height: 567px;
         width: 567px;
         border: 1px solid black;
         -moz-border-radius: 284px;
         -webkit-border-radius: 284px;
         -o-border-radius: 284px;
         border-radius: 284px;
      }
   </style>
   <script type="text/javascript">
   window.onload = function()
      {
         if(document.getElementsByTagName('body')[0].style.MozTransform == ''
            || document.getElementsByTagName('body')[0].style.WebkitTransform == ''
            || document.getElementsByTagName('body')[0].style.OTransform == ''
            || document.getElementsByTagName('body')[0].style.OTransform
            || document.getElementsByTagName('body')[0].style.transform == '')
         {
            var aigS = document.createElement('img');
               aigS.setAttribute('src'  , 'aigS.png');
               aigS.setAttribute('class', 'aig');
               aigS.setAttribute('alt'  , 'aiguille des secondes');
               aigS.setAttribute('style', 'left: 283px;');

            var aigM = document.createElement('img');
               aigM.setAttribute('src'  , 'aigM.png');
               aigM.setAttribute('class', 'aig');
               aigM.setAttribute('alt'  , 'aiguille des minutes');
               aigM.setAttribute('style', 'left: 278px;');

            var aigH = document.createElement('img');
               aigH.setAttribute('src'  , 'aigH.png');
               aigH.setAttribute('class', 'aig');
               aigH.setAttribute('alt'  , 'aiguille des heures');
               aigH.setAttribute('style', 'left: 278px;');

            var horloge = document.getElementById('horloge');
               horloge.appendChild(aigS);
               horloge.appendChild(aigM);
               horloge.appendChild(aigH);
               horloge.setAttribute('style', 'background: url(\'cadran.png\');');

            var d    = new Date();
            var time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();

            aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
            aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';

            setInterval(function() {
               time++;
               aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
               aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
               aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
               aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
               aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
               aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
               aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
               aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
               aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
               aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
               aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
               aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
            }, 1000);
         }
      };
   </script>
</head>
<body>
   <div id="horloge">
   </div>
</body>
</html>


Alléger son code


Ici, on va voir comment faire pour que le script prenne un peu moins de place sans être pour autant illisible. :)
En fait, pour l'instant, il n'y a qu'une chose à faire : il y a deux fois exactement le même groupe d'instructions dans le script. On va donc créer une fonction qui exécutera ces instructions :
Code : JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
window.onload = function()
   {
      
      if(document.getElementsByTagName('body')[0].style.MozTransform == ''
         || document.getElementsByTagName('body')[0].style.WebkitTransform == ''
         || document.getElementsByTagName('body')[0].style.OTransform == ''
         || document.getElementsByTagName('body')[0].style.OTransform
         || document.getElementsByTagName('body')[0].style.transform == '')
      {
         function aig()
         {
            aigS.style.MozTransform    = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.WebkitTransform = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.OTransform      = 'rotate(' + (time * 6) + 'deg)';
            aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
            aigM.style.MozTransform    = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.WebkitTransform = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.OTransform      = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
            aigH.style.MozTransform    = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.WebkitTransform = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.OTransform      = 'rotate(' + Math.round(time / 120) + 'deg)';
            aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
         }

         var aigS = document.createElement('img');
            aigS.setAttribute('src'  , 'aigS.png');
            aigS.setAttribute('class', 'aig');
            aigS.setAttribute('alt'  , 'aiguille des secondes');
            aigS.setAttribute('style', 'left: 283px;');

         var aigM = document.createElement('img');
            aigM.setAttribute('src'  , 'aigM.png');
            aigM.setAttribute('class', 'aig');
            aigM.setAttribute('alt'  , 'aiguille des minutes');
            aigM.setAttribute('style', 'left: 278px;');

         var aigH = document.createElement('img');
            aigH.setAttribute('src'  , 'aigH.png');
            aigH.setAttribute('class', 'aig');
            aigH.setAttribute('alt'  , 'aiguille des heures');
            aigH.setAttribute('style', 'left: 278px;');

         var horloge = document.getElementById('horloge');
            horloge.appendChild(aigS);
            horloge.appendChild(aigM);
            horloge.appendChild(aigH);
            horloge.setAttribute('style', 'background: url(\'cadran.png\');');

         var d    = new Date();
         var time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();
         aig();
         setInterval(function() {
            time++;
            aig();
         }, 1000);
      }
   };


Et tant qu'on y est, on va éviter au navigateur de recalculer 4 fois les mêmes choses :
Code : JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function aig()
{
   aigS.style.MozTransform    =
   aigS.style.WebkitTransform =
   aigS.style.OTransform      =
   aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';
   aigM.style.MozTransform    =
   aigM.style.WebkitTransform =
   aigM.style.OTransform      =
   aigM.style.transform       = 'rotate(' + Math.round(time / 10) + 'deg)';
   aigH.style.MozTransform    =
   aigH.style.WebkitTransform =
   aigH.style.OTransform      =
   aigH.style.transform       = 'rotate(' + Math.round(time / 120) + 'deg)';
}


Je vous laisse réfléchir à ce que ça fait exactement. Si vous ne trouvez pas, vous avez la réponse en dessous :
Secret (cliquez pour afficher)

Ceci est une seule instruction (regardez bien le point virgule) :
Code : JavaScript
1
2
3
4
aigS.style.MozTransform    =
aigS.style.WebkitTransform =
aigS.style.OTransform      =
aigS.style.transform       = 'rotate(' + (time * 6) + 'deg)';


Quand il va analyser ça, le navigateur va d'abord voire que c'est une assignation et va calculer ce qui ce trouve à droite du signe « = » et va se retrouver avec une autre assignation qu'il va traiter de la même façon. Avec des parenthèses et de l'indentation, ça donne ça :
Code : JavaScript
1
2
3
4
5
6
7
8
aigS.style.MozTransform =
(
   aigS.style.WebkitTransform =
   (
      aigS.style.OTransform =
         (aigS.style.transform = 'rotate(' + (time * 6) + 'deg)')
   )
);



Garder la pendule à l'heure


Encore un dernier petit truc : la fonction setInterval peut se décaler assez rapidement pour arriver à un décalage de une à deux minute(s) en une heure (au pire). On va donc régler ce problème en ajoutant cette ligne de code à la fin du script précédent :
Code : JavaScript
1
2
3
4
setInterval(function() {
   d    = new Date();
   time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();
}, 60000);


Vous avez la version finale ici.

À vous !


On a enfin réussi à terminer cette horloge, mais il reste encore des améliorations possibles. Voici une liste non exhaustive de ce que vous pouvez améliorer :
  • Réduire la taille de l'horloge (notamment du cadran) ;
  • permettre à l'utilisateur de déplacer l'horloge sur la page ;
  • permettre à l'utilisateur de cacher l'horloge ;
  • faire un cadran et des aiguilles plus jolis ;
  • afficher l'heure en digital pour ceux qui ne peuvent pas avoir l'horloge ;

Q.C.M.

Pourquoi certaines propriétés de CSS commencent par -moz- ou -webkit- ?
Quel est l'utilité de ce code :

Code : JavaScript
1
2
3
4
setInterval(function() {
   d    = new Date();
   time = d.getSeconds() + 60 * d.getMinutes() + 3600 * d.getHours();
}, 60000);

Statistiques de réponses au QCM

Maintenant, vous savez comment faire une jolie horloge pour votre site. Mais surtout, vous connaissez de nouvelles propriétés CSS (et peut-être de nouvelles choses en Javascript).
Je vous invite à aller regarder les autres propriétés disponibles via -webkit-* et -moz-* (et aussi -o-* et -ms-*) grâce à ces liens (merci à Thunderseb) :

Voici une autre horloge également réalisée pour des sites web, mais avec une autre technique :

Et voici un site (en anglais) parlant du CSS3 si vous voulez aller plus loin.

Si vous constatez une erreur ou une inexactitude dans ce tutoriel, ou bien que vous avez des remarques à faire, n'hésitez pas à poster un commentaire.

Afin de faire fonctionner cette horloge avec Internet Explorer, il faut utiliser le filtre matrix qui est une "extension" de la CSS spécifique à Internet Explorer. Vous avez un petit article qui en parle (à la fin) : Rotation d’éléments avec CSS3. Et comble du bonheur, si vous n'y arrivez toujours pas, un sauveur a fait un plugin jQuery qui s'occupe de tout : Transformie (en anglais).
Pour les allergiques à jQuery, j'ai aussi trouvé cssSandpaper (en) qui vous permettra d'utiliser transform et d'autres propriétés CSS3 avec Internet Explorer.

Partager

46 commentaires pour "Créer une horloge animée avec CSS3 et Javascript"
Note moyenne : 3.26 / 4 (23 votes)
Pseudo Commentaire
Hors ligne berdes1 # Posté le 29/09/2010 à 15:31:38
Avatar

Avis : Très bon
Flux RSS

Ville : Bon encontre
Pays : France métropolitaine

@007Julien : désolé, mais le principe n'est pas du tout le même sur l'exemple que tu as montré vu qu'il a été fait en SVG.

@mdr1 : étonnant :p , tu le fait avec quel navigateur? Faut quand même savoir que chaque navigateur as ses comportements particulier pour ce genre de choses (sélection, par exemple). Je sait que pour google chrome, la sélection sera bonne et correspondra au texte, alors qu'il me semble que firefox, dans une des première version à supporter les transformation, sélectionnait les zones où était le texte avant la rotation.

Image utilisateur Image utilisateur
 
Hors ligne Dark-Prince # Posté le 30/01/2011 à 11:19:42
Avatar

Études : BTS IRIS

Très bon tutoriel mais je pense que mettre ton CSS dans un autre fichier allourdirait moins ton fichier source, sa prend une ligne si tu utilise la balise <link></link>
Hors ligne Arcko # Posté le 30/01/2011 à 12:00:15
Arcko le veau
Avatar

Pas mal comme tuto !

Le programmeur aura une place au paradis...
http://www.megaupload.com/?d=ZDD2YD97 Un petit jeu fait par moi ^^
arrête de lire ca fait mal aux yeux !!! :)
 
Hors ligne moksx # Posté le 24/06/2011 à 23:50:49

BOnjour à tous,

j'ai bien essayé d'adapter ce code, mais c'est pas évident: Je souhaiterais que mon image se déplace verticalement (avec transform:translate) seconde par seconde (voire plus vite) en affichant une position en px en fonction de l'heure (réelle) d'affichage de la page (avec setInterval). Il y a probablement tous les éléments dans ce tuto... Si quelqu'un a une idée

Merci d'avance :D
Moksx
Hors ligne Amin Ghannay # Posté le 11/03/2012 à 23:15:47
Avatar

Avis : Bon

Ville : Strasbourg
Pays : France métropolitaine

Bonjour, j'ai un petit problème, quand je met le code dans ma page html, j'ai juste le mot horloge qui s'affiche.

Je ne comprends pas, j'ai la dernière version de google chrome.

Est-ce que quelqu'un peut m'expliquer ce qui ne va pas ?

Je sais développer des sites web, des bases de données. Je sais utiliser la plupart des logiciel de graphisme et je programme en Ti-Basic, Casio-Basic, Algol, Fortran, Pascal, Ada, Modula, Oberon.
Toujours là si vous avez des projets.
 

Voir tous les commentaires
Ce tutoriel a été corrigé par les zCorrecteurs.