Monorailcat

Horloge CSS Javascript

icon 2022-10-09 - No comments

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 :
  • 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
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'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.
Horloge SBB 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 à 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.
Horloge SBB finale

icon Tags de l'article : , ,

Disque de stationnement

icon 2017-06-18 - No comments

Après m'être fait rappeller à l'ordre par la police, j'ai dû chercher un disque de stationnement officiel.

Il en existe de plein de sortes, mais tant qu'à faire, autant ne pas perdre nos habitudes de LaTeX/Tikz pour avoir un disque multilingue.
Parking disk

Normes
Le code de la route Allemand (duquel sont repris les disques de parkings Européens) donne les standards à respecter:
  • Dimensions: 110 × 150mm
  • Couleurs: Noir, Blanc et bleu signal (RAL 5005)
  • Police: DIN 1451 (une police sans serif suffit)

LaTeX
Le plus simple est de séparer le code en deux images Tikz, pour le disque et la pièce qui va le tenir.
Au début, on va s'aider de grilles pour vérifier la géométrie.
\draw[step=1cm, gray, very thin] (-6,-6) grid (6,6);

Disque
On forme un cercle de 5.25cm de diamètre et un disque de 3mm de diamètre, rempli en noir, serviront de repères pour découper et centrer le disque.

\draw (0,0) circle (5.25cm);
\fill[black] (0,0) circle (0.3cm);

Après, on trace 12 rayons pour chaque heure, en indiquant l'heure correspondante (sans oublier d'aligner les caractères perpendiculairement aux rayons).
Tant qu'à faire, on trace aussi des rayons plus fins toutes les demi-heures.
\foreach \angle [count=\xi] in {30,60,...,360} {
	\draw[line width=.1cm] (\angle:1.5cm) -- (\angle:3cm);
	\draw[line width=.1cm] (\angle:4cm) -- (\angle:5cm);
	\node[rotate=30*\xi+90,font=\Huge\sffamily\bf] at (\angle:3.5cm) {\textsf{\xi}};
}

\foreach \angle in {15,30,...,360} {
	\draw[line width=.01cm] (\angle:1.5cm) -- (\angle:3cm);
}

Une fois tout terminé, ça devrait ressembler à ça :
clock

Support
On va organiser la pièce en superposant des formes les unes sur les autres.

On commence par deux rectangles bleus de 110*150mm:
\fill[signalblau] (11,0) rectangle (22,15);
\fill[signalblau] (0,0) rectangle (11,15);
Rectangle shape

On rajoute deux coupures en haut du support, pour tourner le disque. À noter que la zone est hachurée, comme toutes les zones que l'on va découper:
\filldraw[white] (1.5,15) arc (240:300:8cm);
\filldraw[white] (12.5,15) arc (240:300:8cm);
\draw[GridSize=0.5cm, pattern=Hatchgrid, pattern color=black] (1.5,15) arc (240:300:8cm);
\draw[GridSize=0.5cm, pattern=Hatchgrid, pattern color=black] (12.5,15) arc (240:300:8cm);
thumbwheel cutouts

On découpe une fenêtre en arc de disque pour voir la portion du disque qui nous intéresse. Comme le fait d'avoir un polygone avec cette forme est difficile, on va superposer plusieurs polygones:
\fill[white] (5.5,10) ++(210:4cm) arc (210:330:4cm) -- (6.8,9.25) (5.5,10) ++(330:1.5cm) arc (330:210:1.5cm) -- (2.03,8);
\draw[GridSize=0.5cm, pattern=Hatchgrid, pattern color=black] (5.5,10) ++(210:4cm) arc (210:330:4cm) -- (6.8,9.25) (5.5,10) ++(330:1.5cm) arc (330:210:1.5cm) -- (2.03,8);
\fill[signalblau] (5.5,10) ++(210:1.5cm) arc (210:330:1.5cm);  %dirty workaround
\draw (5.5,10) ++(210:1.5cm) arc (210:330:1.5cm);
Clock window

On rajoute des traits de construction autour des rectangles:
\draw (0,0) -- (11,0) -- (11,15) -- (0,15) -- cycle;
\draw (11,0) -- (22,0) -- (22,15) -- (11,15) -- cycle;

Dessiner un signe de parking est plus facile qu'il en a l'air:
\draw [rounded corners=0.5cm, white, line width=0.25cm] (3.5,1) rectangle (7.5, 5);
\draw [white, line width=0.5cm] (5,1.5) -- (5,4.25);
\draw [white, line width=0.5cm] (5,4) -- (5.75,4);
\draw [white, line width=0.5cm] (5,3) -- (5.75,3);
\draw [white, line width=0.5cm] (5.75,4) arc (90:-90:0.5cm);
Parking sign

Il reste à rajouter un triangle blanc pour indiquer l'heure et un repère pour le pivot/axe du disque:
\filldraw[white] (5.5, 9) -- (5, 11) -- (6, 11) -- cycle; 

\filldraw[black] (5.5,10) circle (3mm);
\filldraw[black] (16.5,10) circle (3mm);
Arrow + pivot point

On doit rajouter du texte (adaptable en fonction des langues choisies) et quelques notes au dos:
\node[below, align=center, white, font=\small\sffamily] at (5.5,13.5) {\Huge Time of arrival\\\Huge Ankunftszeit\\\Huge Heure d'arrivée};
%\node[below, align=center, white] at (5.5,13.5) {\Huge TIME OF ARRIVAL\\\Huge ANKUNFTSZEIT\\\Huge HEURE D'ARRIVÉE};

\node[align=center, white] at (16.5,3) {\small Xavier Bourgeois\\http://monorailc.at/\\nospam@monorailc.at};
\node[below] at (20,3) {\includegraphics[width=1.5cm]{Cc-by-nc-sa_icon.png}};
Text

Et pour finir, les notes pour couper, coller et plier le support:
\node[below, fill=white] at (5.5, 8) {cut this hatched area};
\node[below, fill=white] at (5.5, 14.75) {cut this hatched area};
\node[below, fill=white] at (16.5, 14.75) {cut this hatched area};
\draw[GridSize=0.25cm, pattern=Hatchgrid, pattern color=signalblau] (0,0) -- (-2,1) -- (-2,14) -- (0,15);
\node[below,rotate=90, fill=white] at (-1, 7.5) {fold and glue this hatched area};
\draw[GridSize=0.25cm, pattern=Hatchgrid, pattern color=signalblau] (0,0) -- (1,-2) -- (10,-2) -- (11,0);
\node[below, fill=white]at (5.5, -1) {fold and glue this hatched area};

\node[right] at (13,16) {fold here};
\node[right] at (13,-1) {fold here};
\draw[->, bend right] (13,16) to (11,15.1);
\draw[->, bend left] (13,-1) to (11,-.1);

\node[right] at (2,16) {fold here};
\node[left] at (-1,-1) {fold here};
\draw[->, bend right] (2,16) to (0,15.1);
\draw[->, bend right] (-1,-1) to (0,-.1);
Notes

À noter qu'il a fallu définir la couleur "Bleu Signal" et le remplissage "hachuré":
\definecolor{signalblau}{HTML}{005387}

\pgfdeclarepatternformonly[\GridSize]{Hatchgrid}{\pgfqpoint{-1cm}{-1cm}}{\pgfqpoint{4cm}{4cm}}{\pgfqpoint{\GridSize}{\GridSize}}
{
	\pgfsetlinewidth{0.01cm}
	\pgfpathmoveto{\pgfqpoint{0pt}{0pt}}
	\pgfpathlineto{\pgfqpoint{1cm}{1cm}}
	\pgfusepath{stroke}
}
\newdimen\GridSize
\tikzset{
	GridSize/.code={\GridSize=#1},
	GridSize=3pt
}

Une version prête à imprimer est disponible: Disque de parking

Références

icon Tags de l'article : , ,