En tant que développeur, je suis très souvent à la recherche de nouvelles idées, de nouvelles façons de faire, … C’est pourquoi depuis peu je traîne pas mal sur codepen qui est une excellente source d’inspiration et d’étonnement. C’est en me promenant dessus il y a quelques semaines que je suis tombé sur ce chronomètre interactif fait en SVG et en utilisant du JS. Je me suis alors demandé si la même chose était possible uniquement en CSS (parce que soyons honnête, c’est tout de même moins drôle en JS). Voici donc un tutoriel pour apprendre à créer un chronomètre interactif uniquement en HTML/CSS !

(cliquez sur le bouton rouge pour activer ou éteindre le chronomètre)

La base de notre chronomètre

Pour ce qui est du HTML j’ai essayé de faire simple: un input caché (nous reviendrons plus tard sur son utilité) et une div contenant les différents éléments de notre chronomètre: une div pour le cercle du dessus (celui qui permet d’accrocher par exemple une chaîne), une div pour le bouton du dessus (qui ne marchera pas), un label pour notre bouton fonctionnel, une div pour la grosse partie du chronomètre et une dernière pour l’aiguille !

<input type="checkbox" id="run">
<div class="chrono">
	<div class="circle"></div>
	<div class="top-btn"></div>
	<label for="run" class="btn"></label>
	<div class="center"></div>
	<div class="needle"></div>
</div>

Notez que nous lions le label à l’input grâce à son attribut « for » de façon à cocher/décocher l’input juste en cliquant sur notre label. Nous allons commencer par cacher notre input, je prépare également le body pour que le chronomètre soit centré verticalement et horizontalement:

body {
	margin: 0;
	height: 100vh;
	display: flex;
	justify-content: center;
	align-items: center;
}
input {
	display: none;
}

Le chronomètre

Nous allons maintenant pouvoir commencer au design de notre chronomètre. Commençons par le cercle du dessus. Nous allons dessiner un cercle de 40*40px avec donc un border-radius de 50% et une bordure de 4px noire. Afin de ne pas avoir à utiliser trop d’éléments HTML nous allons jouer avec les ombres pour la suite. En effet, un élément peut avoir autant d’ombre que vous voulez avec la couleur et la taille que vous souhaitez. Nous allons donc rajouter deux ombres: une grise de 5px de large pour l’intérieur du cercle puis une seconde de 9px noire pour la seconde bordure (la taille est de 9px pour que la première ombre qui est dessus ne laisse apparaître qu’une bordure de 4px de large). Nous le mettons ensuite en position: absolute et le positionnons où nous voulons.

J’ai décidé pour ce projet d’avoir le centre du chronomètre en position 0,0. J’ai donc mis un top et un left en conséquence.

.chrono {
	position: relative;
}
.chrono .circle {
	height: 40px;
	width: 40px;
	border-radius: 50%;
	border: 4px solid #000;
	box-shadow:
		0 0 0 5px #DDD,
		0 0 0 9px #000;
	position: absolute;
	top: -185px;
	left: -20px;
}

Nous allons maintenant passer à la partie qui relie le cercle au chronomètre (et qui est généralement un bouton, je l’ai donc nommé top-btn même s’il n’est pas utilisable sur ce projet). Nous allons le séparer en deux parties: la partie reliée au chronomètre qui est légèrement plus fine, et la partie reliée au cercle du haut.

Pour la première partie, nous dessinons simplement un rectangle de 20*26px gris. Nous mettons ensuite sa bordure en solid et noir, puis sa border-width à « 0 4px » afin de n’avoir de visible que les bordures droites et gauche (les bordures s’écrivent dans le sens des aiguilles d’une montre en partant et vont par paire si les 4 valeurs ne sont pas données). Nous le positionnons ensuite à l’endroit souhaité:

.chrono .top-btn {
	width: 26px;
	height: 20px;
	background-color: #DDD;
	border: solid #000;
	border-width: 0 4px;
	position: absolute;
	left: -13px;
	top: -135px;
}

Pour la seconde partie, nous allons créer un pseudo-élément à partir de ce bouton. Comme tous les pseudos-élément nous devons commencer par lui donner un content que nous laisserons vide ici. Nous dessinons un rectangle de 36*15px avec un fond gris et une bordure de 4px noire. Nous ajoutons un léger border-radius de 5px puis nous le positionnons correctement, par dessus son parent:

.chrono .top-btn:before {
    content: "";
    width: 36px;
    height: 15px;
    border: 4px solid #000;
    border-radius: 5px;
    background-color: #DDD;
    position: absolute;
    top: -15px;
    left: -9px;
}

Pour rester dans les boutons, nous allons passer à notre label qui permettra de déclencher et stopper notre chronomètre. Nous dessinons un nouveau rectangle de 36*15px avec ici un fond rouge (j’ai utilisé le #F44336) et une bordure de 4px noire. Nous y ajoutons également un border-radius de 5px. Comme le bouton est cliquable, nous ajoutons un cursor: pointer pour indiquer à l’utilisateur qu’il peut interagir avec.

Comme il se trouvera sur le côté de notre chronomètre qui est rond, nous allons devoir le pencher. L’angle dépendra de la position que vous lui donnerez. Si vous n’avez pas utilisé les même top et left que moi pour les autres éléments, je vous conseille d’attendre d’avoir dessiné le rond central pour positionner ce bouton.

.chrono .btn {
    width: 36px;
    height: 15px;
	background-color: #F44336;
    border: 4px solid #000;
    border-radius: 5px;
    cursor: pointer;
    transform: rotate(40deg);
    position: absolute;
    top: -105px;
    left: 60px;
}

Et enfin: le centre

Et nous allons maintenant pouvoir passer à la partie centrale de notre chronomètre ! Nous dessinons un rond de 200*200px avec un fond gris et une bordure noire de 4px. Comme pour le premier cercle, nous allons jouer avec les ombres pour les autres bordures. Nous dessinons donc une première ombre de 15px en bleu (j’ai utilisé le #03A9F4) et une seconde de 19px en noir.

.chrono .center {
	height: 200px;
	width: 200px;
	background-color: #EEE;
	border: 4px solid #000;
	border-radius: 50%;
	box-shadow:
		0 0 0 15px #03A9F4,
		0 0 0 19px #000;
	position: absolute;
	top: -100px;
	left: -100px;
}

Et voici donc le dernier élément de notre chronomètre: l’aiguille ! Nous allons la séparer en deux parties: un cercle au centre d’où partira une tige en pseudo-élément.

Le cercle fera 10*10px et sera noir. Et comme il s’agit de l’élément principal de notre aiguille, c’est lui qui contiendra l’animation ! Nous allons donc lui assigner l’animation (que j’ai ici nommée « run ») avec une durée de 60s. Nous voulons que l’animation soit linéaire et qu’elle se répète à l’infini. Et comme le chronomètre doit être arrêté au chargement de la page, nous lui ajoutons un animation-play-state à paused.

.chrono .needle {
	width: 10px;
	height: 10px;
	background-color: #000;
	border-radius: 50%;
	animation: run 60s linear infinite paused;
}

Comme l’animation est assez simple, nous allons utiliser la syntaxe « from – to » de la keyframe qui permet de donner un état de départ et d’arrivée. Et comme notre « from » est le même que l’état normal de notre aiguille, nous avons juste besoin de préciser le « to » dans notre CSS. Nous effectuons donc une rotation complète comme ceci:

@keyframes run {
	to {
		transform: rotate(360deg);
	}
}

Pour la tige de l’aiguille, nous dessinons enfin un rectangle de 2*90px noir et le positionnons comme voulu:

.chrono .needle:before {
	content: "";
	height: 90px;
	width: 2px;
	background-color: #000;
	position: absolute;
	bottom: -10px;
	left: 4px;
}

Déclencher l’animation

Nous voulons maintenant qu’en cliquant sur le label, l’animation se déclenche. Nous avons un input caché qui s’active au clic sur notre label, nous allons nous en servir pour déclencher l’animation. Pour cela nous allons utiliser deux sélecteurs intéressants:

  • la pseudo-classe :checked qui permet d’appliquer du style seulement quand une checkbox ou un radio est cochée
  • le « + » qui permet de sélectionner un élément frère (donc contenu dans le même élément HTML) qui suit directement ce qui est indiqué à sa gauche.

L’idée ici est de préciser qu’on veut récupérer l’élément situé après un input au moment où cet input est coché et déclencher l’animation de l’élément .needle de cet élément que l’on vient de récupérer, ce qui se fait comme ceci:

input:checked + .chrono .needle {
	animation-play-state: running;
}

Ici nous récupérons donc l’input quand il est coché, nous récupérons l’élément .chrono situé juste après, et enfin l’élément .needle contenu dans ce .chono.

Afin de mieux indiquer quand le chrono est lancé ou non, nous allons utilisé la même technique pour donner un effet « enclenché » à notre label en le re-positionnant:

input:checked + .chrono .btn {
    left: 58px;
    top: -102px;
}

Notez que nous aurions pu mettre l’input directement dans le chrono pour éviter d’avoir à passer par l’élément chrono. Je ne l’ai pas fait car pour ce genre de projet je préfère avoir mon élément visible seul et l’input déclencheur à part. Si vous préférez avoir tout le projet dans le même conteneur, vous pouvez placer votre input tout en haut dans votre élément .chrono. Le sélecteur « + » ne va alors plus marcher (il ne sélectionne qu’un frère situé directement après) mais en passant par le sélecteur « ~ » vous pouvez corriger ce soucis. Le « ~ » a la même fonction que le « + » mais sélectionne tous les éléments suivant ce que vous mettez à gauche. Vous obtiendrez donc quelque chose comme ceci:

input:checked ~ .btn {
	left: 58px;
    top: -102px;
}
input:checked ~ .needle {
	animation-play-state: running;
}

Vous voilà désormais avec un magnifique chronomètre entièrement fonctionnel ! Si vous voulez vous entraîner encore en CSS, pas d’inquiétudes: de nombreux tutoriels vont arriver par la suite pour parler plus en détail de certaines notions ou propriétés.

Si vous avez des besoins en intégration, n’hésitez pas à me contacter afin que nous puissions discuter de votre projet !