Créer une timeline avec uniquement du CSS !
Tags: css, frontend, web, french
Published: 2023-07-18 08:00:00

Parfois on a pas forcément besoin de quelque chose de très compliqué, mais on veut un truc propre pour présenter des dates. Je vous propose de réaliser une timeline uniquement avec du CSS avec aucun élément parasite dans le code HTML pour avoir quelque chose de super simple.

> Note : la magie du CSS opérant, vous pouvez copier-coller les extraits de CSS au fur et à mesure sans fusionner les sélecteurs, ça va fonctionner parfaitement ! Donc profitez-en pour vous concentrer sur ce qu'il se passe 😉

## La base : les données

Alors pour mon exemple je vais partir de la liste des sorties des consoles Nintendo (à partir de la NES), parce que pourquoi pas ? (et puis j'aime les jeux-vidéos et leur histoire 😇). Donc voici cette liste brute :

```text
Fin octobre 1987 Nintendo Entertainment System
28 septembre 1990 Game Boy
11 avril 1992 Super Nintendo
Fin 1996 Game Boy Pocket
1er septembre 1997 Nintendo 64
23 novembre 1998 Game Boy Color
22 juin 2001 Game Boy Advance
3 mai 2002 GameCube
28 mars 2003 Game Boy Advance SP
11 mars 2005 Nintendo DS
4 novembre 2005 Game Boy Micro
11 juin 2006 Nintendo DS Lite
7 décembre 2006 Wii
3 avril 2009 Nintendo DSi
5 mars 2010 Nintendo DSi XL
25 mars 2011 Nintendo 3DS
28 juillet 2012 Nintendo 3DS XL
30 novembre 2012 Wii U
3 mars 2017 Nintendo Switch
20 septembre 2019 Nintendo Switch Lite
8 octobre 2021 Nintendo Switch OLED
```

## Comment afficher un ensemble de dates ?

L'option qu'on va retrouver le plus pour faire ça ce sera le tableau. C'est le choix qu'on retrouve sur Wikipédia (la source de mes données) par exemple, mais ce n'est pas forcément le plus sympa visuellement. De plus on a uniquement des dates et des noms de console à mettre en face, donc on aurait un tableau à deux colonnes, pas vraiment nécessaire.

Personnellement je vous propose d'utiliser une liste. En HTML, on a des balises pour ça `<ul>` (= Unordered List) et `<li>` (= List Item) : 

```html
<ul>
    <li>Fin octobre 1987 Nintendo Entertainment System</li>
    <li>28 septembre 1990 Game Boy</li>
    <li>11 avril 1992 Super Nintendo</li>
    <li>Fin 1996 Game Boy Pocket</li>
    <li>1er septembre 1997 Nintendo 64</li>
    <li>23 novembre 1998 Game Boy Color</li>
    <li>22 juin 2001 Game Boy Advance</li>
    <li>3 mai 2002 GameCube</li>
    <li>28 mars 2003 Game Boy Advance SP</li>
    <li>11 mars 2005 Nintendo DS</li>
    <li>4 novembre 2005 Game Boy Micro</li>
    <li>11 juin 2006 Nintendo DS Lite</li>
    <li>7 décembre 2006 Wii</li>
    <li>3 avril 2009 Nintendo DSi</li>
    <li>5 mars 2010 Nintendo DSi XL</li>
    <li>25 mars 2011 Nintendo 3DS</li>
    <li>28 juillet 2012 Nintendo 3DS XL</li>
    <li>30 novembre 2012 Wii U</li>
    <li>3 mars 2017 Nintendo Switch</li>
    <li>20 septembre 2019 Nintendo Switch Lite</li>
    <li>8 octobre 2021 Nintendo Switch OLED</li>
</ul>
```

Bon on a commencé à structurer un peu, mais ce n'est toujours pas la folie : on ne distingue pas les dates, on a une bête liste à puce.

## Parenthèse balise oublié : `<time>`

Cette balise est là depuis quelques années déjà (dispo dans Firefox dès 2013, elle est dans Chrome depuis fin 2017), on l'utilise peu alors que c'est un super outil ! Globalement elle fait partie des balises créées pour donner de la sémantique à notre contenu. L'idée ici est d'indiquer qu'un texte représente une date, et donner une indication plus orientée "machine" de notre date via l'attribut `datetime`.

Dans notre cas, on peut transformer `Fin octobre 1987 Nintendo Entertainment System` en `<time datetime="1987-10">Fin octobre 1987</time> Nintendo Entertainment System` ou `28 septembre 1990 Game Boy` en `<time datetime="1990-09-28">28 septembre 1990</time> Game Boy`. Il y a plein de format autorisé de date, le texte affiché restant totalement libre. Mais on peut imaginer pouvoir greffer des Web Extension qui vont reformater les dates à format qui nous convient mieux dans les pages avec ce système (on se rappelle qu'en France on utilise des dates au format `dd/MM/yyyy`, aux USA c'est `yyyy/MM/dd`, au Japon c'est `yyyy-MM-dd`).

En tout cas je propose qu'on ré-écrive notre liste comme ceci :

```html
<ul>
    <li><time datetime="1987-10">Fin octobre 1987</time> Nintendo Entertainment System</li>
    <li><time datetime="1990-09-28">28 septembre 1990</time> Game Boy</li>
    <li><time datetime="1992-04-11">11 avril 1992</time> Super Nintendo</li>
    <li><time datetime="1996">Fin 1996</time> Game Boy Pocket</li>
    <li><time datetime="1997-09-01">1er septembre 1997</time> Nintendo 64</li>
    <li><time datetime="1998-11-23">23 novembre 1998</time> Game Boy Color</li>
    <li><time datetime="2001(06-22)">22 juin 2001</time> Game Boy Advance</li>
    <li><time datetime="2002-05-03">3 mai 2002</time> GameCube</li>
    <li><time datetime="2003-03-28">28 mars 2003</time> Game Boy Advance SP</li>
    <li><time datetime="2005-03-11">11 mars 2005</time> Nintendo DS</li>
    <li><time datetime="2005-11-04">4 novembre 2005</time> Game Boy Micro</li>
    <li><time datetime="2006-06-11">11 juin 2006</time> Nintendo DS Lite</li>
    <li><time datetime="2006-12-07">7 décembre 2006</time> Wii</li>
    <li><time datetime="2009-04-03">3 avril 2009</time> Nintendo DSi</li>
    <li><time datetime="2010-03-05">5 mars 2010</time> Nintendo DSi XL</li>
    <li><time datetime="2011-03-25">25 mars 2011</time> Nintendo 3DS</li>
    <li><time datetime="2012-07-28">28 juillet 2012</time> Nintendo 3DS XL</li>
    <li><time datetime="2012-11-30">30 novembre 2012</time> Wii U</li>
    <li><time datetime="2017-03-03">3 mars 2017</time> Nintendo Switch</li>
    <li><time datetime="2019-09-20">20 septembre 2019</time> Nintendo Switch Lite</li>
    <li><time datetime="2021-10-08">8 octobre 2021</time> Nintendo Switch OLED</li>
</ul>
```

Ce qui visuellement ne change absolument rien mais va nous servir plus tard !

![Capture de la liste avec les balises `<time>`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lmoyxzf1sbkif3ci973b.jpg)

## Les points c'est moche…

Alors on en convient, une timeline on l'imagine souvent avec une ligne sur laquelle on va placer des cercles dessus pour indiquer où se place la date. Donc on va commencer par remplacer les points par des cercles bleus (pourquoi bleu ? Pourquoi pas ?)

Pour ça on va commencer par ne plus afficher le point avec `list-style-type: none`, puis on va fabriquer en CSS un pseudo-élément avant chaque élément de notre liste. En effet, pour chaque élément html, on peut cibler en CSS l'élément `::before`, définir son contenu (pour lui donner une existance dans le DOM), puis ajouter du style. Pour le style de l'élément `::before`, on définit une taille, une bordure bleue, un arrière-plan blanc (sinon on verra à travers le cercle) et passer la bordure en arrondi sur la moitié de la taille de l'élément. On va aussi devoir le passer en `inline-block` pour que l'élément reste aligné avec le texte mais pouvoir définir sa taille (sinon un élément `::before` ou `::after` est inline).

Si on compile tout ça sous forme de code, ça donne ça :

```CSS
ul {
    list-style-type: none;
}
ul li::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    border: 4px solid #20a2fd;
    background-color: white;
    border-radius: 50%;
}
```

![Liste avec les puces remplacées par des cercles bleus](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cl3kvee400cay908s232.jpg)

## La ligne verticale

Pour la ligne verticale, on peut opter une l'ajout d'une bordure sur la liste complète, mais je préfère le faire de nouveau à partir d'un pseudo-élément `::before` pour être libre niveau style.

Donc le style qu'on va appliquer : on va mettre une bordure sur la droite du `::before` de la liste, ensuite pour pouvoir positionner cet élément, on va définir l'élément `<ul>` comme `position: relative` (ce faisant les éléments enfants en `position: absolute` seront placés en se basant sur cet élément ensuite), puis on va positionner la ligne un peu au-dessus du haut de la liste, un peu en dessous (ce qui va lui donner la même hauteur que la liste, peu importe sa taille), et à gauche.

Sous forme de code :

```CSS
ul {
    position: relative;
}
ul::before {
    content: "";
    border-right: 4px solid #20a2fd;
    position: absolute;
    top: -4px;
    left: 0;
    bottom: -4px;
}
```

![Maintenant avec la ligne bleue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e9up78dtu3jnwehoph3y.jpg)

## Positionner tout correctement

On peut voir qu'il nous manque un peu de positionnement. En effet les cercles bleus sont collés au texte du fait qu'on ne lui a donné aucune position et qu'ils sont inline, la ligne est complètement décalée à gauche à cause du padding standard qu'on va avoir à gauche d'une liste.

L'idée c'est donc de corriger tout ça en rapprochant la ligne du texte et à l'inverse en éloignant un peu les cercles du texte, le tout en faisant en sorte que les cercles et la ligne soit alignés verticalement mais avec toujours les cercles au-dessus de la ligne.

Les faits ces choix pour que ça fonctionne :
- on ajoute un `position: relative` sur les `<li>`, car qui va augmenter le *stacking context* (on pourrait faire de nombreux articles sur ce sujet… mais en vulgarisant ça revient à dire que le `z-index` des `<li>` sera toujours plus haut que celui de la balise parente, ici `<ul>`)
- on change le padding de la liste pour mettre une valeur plus petite (pour rapprocher la ligne du texte)
- on passe aussi les `li::before` en `display: block` et `position: absolute`, en faisant ça on va pouvoir gérer précisément la position du cercle
- comme pour les parties précédentes, on va jouer sur les padding et les positions pour que tout s'aligne
- on ajoute aussi un peu de padding sous chaque élément pour aérer un peu la timeline


Sous forme de code :

```CSS
ul {
    padding-left: 4px;
}
ul::before {
    bottom: 0;
}
ul li {
    position: relative;
    padding-bottom: 8px;
    padding-left: 16px;
}
ul li::before {
    display: block;
    position: absolute;
    top: 4px;
    left: -10px;
}
```


![Timeline complète](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lxt14kzfvaqt4c8p7oe.jpg)

![Timeline complète](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp6cvhv66eannz0d91rx.jpg)

Ce coup-ci c'est bon on a notre timeline !

## Quelques améliorations possibles

### Utiliser des custom properties

Pour l'article j'ai tout écrit sans variable (custom properties), mais dans un contexte professionnel je passerai par des variables pour rendre le code plus clair et éviter d'avoir des nombres qui sortent de nulle part ou des couleurs en double.

```CSS
ul {
    --timeline--font-size: 16px;
    --timeline--color: #20a2fd;
    --timeline--width: calc(var(--timeline--font-size) / 4);
    --timeline--circle-width: calc(var(--timeline--font-size) / 2);

    list-style-type: none;
    position: relative;
    padding-left: 4px;
    font-size: var(--timeline--font-size);
}
ul::before {
    content: "";
    border-right: var(--timeline--width) solid var(--timeline--color);
    position: absolute;
    top: calc(-1 * var(--timeline--width));
    left: 0;
    bottom: 0;
}
ul li {
    position: relative;
    padding-bottom: 8px;
    padding-left: var(--timeline--font-size);
}
ul li::before {
    content: "";
    display: block;
    width: var(--timeline--circle-width);
    height: var(--timeline--circle-width);
    border: var(--timeline--width) solid var(--timeline--color);
    background-color: white;
    border-radius: 50%;
    position: absolute;
    top: var(--timeline--width);
    left: calc(-1 * (var(--timeline--circle-width) + var(--timeline--width) / 2));
}
```

### Mettre en avant la date

Comme on a placé nos dates dans des balises `<time>`, on va pouvoir les utiliser pour les mettre en évidence et même ajouter un séparateur entre la date le texte.

```CSS
ul li time {
    font-weight: bold;
}
ul li time::after {
    content: ' - ';
}
```

![Avec du style sur la date](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqr3n1a68ck66nplcxpb.jpg)

### Mettre en évidence les décennies

Comme on a une indication de la date, on peut jouer dessus en combinaison avec un sélecteur avec un pattern de type "commence par" (ce qui donne `time[datetime^="199"]` pour les dates qui commence par "199", autrement dit, les dates de la décennie 1990).

C'est suffisant pour colorer la date, par contre si ce n'est pas la date qu'on va chercher à mettre en évidence mais le cercle sur la timeline, alors on va avoir besoin du `+` (qui permet de sélectionner le premier voisin de l'élément pointé avant le `+`) et du pseudo-sélecteur `:has()` (qui permet de valider un sous sélecteur sans changer l'élément pointé).

> Note : le code suivant ne produit aucun résultat sous Firefox, en effet le pseudo-sélecteur `:has()` est encore derrière un flag au moment où j'écris cet article, mais on pourra prochainement l'utiliser !

```CSS
ul li:has(time[datetime^="199"]) + li::before {
  border-color: green;
}
ul li:has(time[datetime^="200"]) + li::before {
  border-color: orange;
}
ul li:has(time[datetime^="201"]) + li::before {
  border-color: red;
}
```

![Résultat sur Chrome](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6fbq5ue4tryj7nitpwa.jpg)


## Conclusion

Je souhaiterais conclure en faisant un rappel car comme toujours à ce qu'on fait avec du CSS, au sens où il ne faut pas communiquer que par des couleurs ou des positions visuelles, en effet notre contenu peut être lu par des gens qui auront des déficiences visuelles (du petit daltonisme à la cécité complète avec tout le spectre qu'on peut imaginer ou non entre les deux), et pour eux aussi il faut que le contenu soit accessible. Ici on a quelque chose qui reste plutôt accessible comme on garde uniquement une liste en termes de DOM, par contre le choix des couleurs pour l'indication visuelle de décennie laisse à désirer (je n'étais pas très inspiré pour les couleurs, donc j'ai fait simple mais ne copiez pas bêtement ces couleurs au-delà du test !).

En tout cas j'espère que je vous aurai appris quelques éléments et que ça vous donnera des idées 😎

Sources :
- [Documentation de la balise `<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
- [Compatibilité navigateur de la balise `<time>`](https://caniuse.com/mdn-html_elements_time)
- [Documentation pour la sélection par attribut](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)
- [Documentation pour le combinateur `+`](https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator)
- [Documentation du pseudo-sélecteur `:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has)
- [Compatibilité navigateur du pseudo-sélecteur `:has()`](https://caniuse.com/css-has)

Crédit photo : https://pixabay.com/photos/typewriter-word-history-5516925/