Dessiner en CSS, c’est facile !

Tout le monde connaît CSS, pour s’être arraché quelques cheveux en essayant d’aligner des éléments, rendre un site responsive ou juste pour essayer de rendre le côté front « plus joli ».

CSS ne sert pas uniquement à présenter correctement la partie front d’une web app et il est possible de faire énormément de choses avec. J’aimerais vous montrer qu’il est possible de dessiner en utilisant CSS.

Résultat du tutoriel : une platine vinyl en CSS

Le résultat final de cet article : une platine vinyle réalisée entièrement en HTML + CSS. Le code que nous allons écrire pas à pas est disponible dans son intégralité ici.

Construction pas à pas

Commençons

Maintenant que nous avons fait ce rappel, nous allons commencer par poser le décor, ou plutôt le fond :

<body>
	<div class="background">
	</div>
</body>
.background {
	background: #23a6d5;
	bottom: 0;
	left: 0;
	position: absolute;
	right: 0;
	top: 0;
}

Nous avons un élément div vide auquel nous avons mis une couleur bleu comme fond.

Pour rendre ce fond un peu plus travaillé, utilisons la propriété linear-gradient ce qui nous donnera un effet dégradé bleu-vert.

<body>
	<div class="gradient-background">
	</div>
</body>
.gradient-background {
	background: linear-gradient( #23a6d5, #23d5ab);
	bottom: 0;
	left: 0;
	position: absolute;
	right: 0;
	top: 0;
}

La fonction linear-gradient prend en paramètre une direction puis une liste de couleurs. La direction peut être donnée de manière littérale (to right, to bottom right, …) ou par un angle (120deg). Par défaut, la direction sera vers le bas (équivalent à to bottom ou 180deg).

La platine vinyle

Nous allons maintenant créer la platine vinyle. En soi, il s’agit d’un simple rectangle. Pour lui donner un aspect un peu plus fini, nous allons lui arrondir les angles en utilisant la propriété border-radius que nous allons fixer à 10%.

<body>
  <div class="gradient-background">
    <div class="turntable">
    </div>
  </div>
</body>
.turntable {
  position: absolute;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 400px;
  height: 300px;
  background: #EBEBEB;
  border-radius: 10%;
}

Le disque

Maintenant, rentrons dans le vif du sujet et créons un disque. Pour créer un disque (donc un cercle), nous créons un carré dont les bords sont totalement arrondis.

<body>
  <div class="gradient-background">
    <div class="turntable">
      <div class="disc">
      </div>
    </div>
  </div>
</body>
.disc {
  position: absolute;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 230px;
  height: 230px;
  background: #2D3E4F;
  border-radius: 50%;
}

.disc::after {
  content: "";
  position: absolute;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  border-radius: 50%;
  width: 8px;
  height: 8px;
  background: #EBEBEB;
  border: solid 2px #DB3A3A;
}

Un disque possède un trou pour pouvoir être placé sur la broche (spindle). Nous avons donc dessiné un petit cercle au centre en utilisant le pseudo-élément ::after (terme sur lequel nous reviendrons en détails plus tard).

L’étiquette

Complétons le disque en lui ajoutant une étiquette. Cette étiquette est composée d’un cercle jaune contenant un cercle jaune avec une bordure rose et d’un demi-cercle rose.

La platine vinyle vierge.

Pour créer l’étiquette du disque, nous utiliserons des pseudo-élément ::before et ::after. (CSS dispose de plusieurs pseudo-éléments mais nous ne verrons que ces deux là ici.)

Un pseudo-élément est un mot-clé ajouté à un sélecteur qui permet de mettre en forme certaines parties de l’élément ciblé par la règle. Ainsi, le pseudo-élément ::first-line permettra de ne cibler que la première ligne d’un élément visé par le sélecteur.
Source : Pseudo-éléments sur MDN

À ne pas confondre avec les pseudo-classes :

Une pseudo-classe est un mot-clé qui peut être ajouté à un sélecteur afin d’indiquer l’état spécifique dans lequel l’élément doit être pour être ciblé par la déclaration. La pseudo-classe :hover, par exemple, permettra d’appliquer une mise en forme spécifique lorsque l’utilisateur survole l’élément ciblé par le sélecteur (changer la couleur d’un bouton par exemple).
Source : Pseudo-classes sur MDN

Les pseudo-élément ::before et ::after vont nous permettre de créer un élément avant (avec ::before) et un élément après (avec ::after) l’élément auquel s’applique le style.

<body>
  <div class="gradient-background">
    <div class="turntable">
      <div class="disc">
        <div class="label">
        </div>
      </div>
    </div>
  </div>
</body>
.label {
  position: absolute;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  border-radius: 50%;
  width: 80px;
  height: 80px;
  background: #FFD166;
}

.label::before,
.label::after {
  content: "";
  position: absolute;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  border-radius: 50%;
}

.label::before {
  width: 70px;
  height: 70px;
  background: #FFD166;
  border: solid 1px #FF4D80;
}

.label::after {
  width: 65px;
  height: 32.5px;
  bottom: 32.5px;
  background: #FF4D80;
  border-radius: 32.5px 32.5px 0 0;
}

Nous avons donc .label qui permet de créer un cercle jaune d’un diamètre de 80px. Le pseudo-élément ::before va créer le cercle jaune plus petit avec une bordure rose. Le pseudo-élément ::after va créer un demi cercle rose.

Comme vous pouvez le remarquer, le demi-cercle est en fait un rectangle avec deux angles arrondis. La déclaration border-radius: 32.5px 32.5px 0 0; indique que les angles du haut ont un arrondi de 32.5px (le diamètre du cercle divisé par 2) et que les deux angles du bas sont à zéro.

On peut voir que la bordure rose a été défini de la manière suivante : border: solid 1px #FF4D80. Il existe plusieurs propriétés permettant d’affecter l’aspect d’une bordure : border-style, border-color, border-width, border-left, … La propriété border permet de regrouper les propriété border-style, border-color et border-width pour permettre de garder un CSS concis.

div {
  border-style: solid;
  border-color: #FF4D80;
  border-width: 1px;
}

…devient alors :

div {
 border: solid 1px #FF4D80;
}

Le titre

Nous allons maintenant ajouter un titre et un groupe à notre disque.

<body>
  <div class="gradient-background">
    <div class="turntable">
      <div class="disc">
        <div class="label">
          <div class="disc-font disc-title">
            CSS rocks!
          </div>
          <div class="disc-font disc-group">
            wubba lubba dub dub
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
.disc-font {
  font-family: Roboto, Arial, Sans-serif;
  font-size: 0.5em;
  text-align: center;
  z-index: 10;
  position: absolute;
  color: #331832;
}

.disc-title {
  margin: 20px;
}

.disc-group {
  top: 50px;
  left: 10px;
  width: 60px;
}

Il s’agit simplement de styliser le texte pour qu’il se positionne correctement et à la bonne taille.

Nous avons choisi d’utiliser la valeur 0.5em pour la propriété font-size. Il est possible d’utiliser d’autres unités comme pixel (px), pourcentage (%), viewport width (vw), etc. L’unité em est celle recommandée par le W3C (1em équivaut à la taille de texte actuel qui est par défaut dans la plupart des navigateurs 16px).

Pour la propriété font-family, nous avons défini trois différentes polices de caractères. Deux polices spécifiques : Roboto et Arial et une famille de police : Sans-serif.

Il est préférable de proposer plusieurs polices car cette propriété fonctionne sur un système de fallback : si la première police n’est pas supportée par le navigateur, il essaiera d’utiliser la deuxième et ainsi de suite. Terminer par une famille de police permet au navigateur de choisir une police de ce type parmis celles dont il dispose.

Il existe trois principaux types de police différents : serif, sans-serif et monospace. Les polices de type serif sont celles qui possèdent des empattements. Les polices de type sans-serif sont celles qui ne possèdent pas d’empattement. Tandis que les polices de type monospace ont la particularité d’avoir chacun de leur caractère de la même largeur.

Les derniers éléments de la platine

Terminons maintenant la platine vinyle en ajoutant le bras mécanique et quelques boutons.

<body>
  <div class="gradient-background">
    <div class="turntable">
      <div class="disc">
        <div class="label">
          <div class="disc-font disc-title">
            CSS rocks!
          </div>
          <div class="disc-font disc-group">
            wubba lubba dub dub
          </div>
        </div>
      </div>
      <div class="buttons">
      </div>
      <div class="tone-arm oscillating">
      </div>
    </div>
  </div>
</body>
.buttons {
  position: absolute;
  margin: auto;
  top: 250px;
  left: 260px;
  bottom: 0;
  right: 0;
  width: 10px;
  height: 10px;
  background: #FECB2F;
  border-radius: 50%;
}

.buttons:before,
.buttons:after {
  content: "";
  position: absolute;
  margin: auto;
  top: 0;
  bottom: 0;
  right: 0;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.buttons:before {
  background: #FF4B3E;
  left: 20px;
}

.buttons:after {
  background: #FF784F;
  left: 40px;
}

.tone-arm {
  position: absolute;
  margin: auto;
  top: 0;
  left: 280px;
  bottom: 220px;
  right: 0;
  width: 20px;
  height: 40px;
  background: #b7b6b6;
  border-radius: 10%;
  transform: rotate(20deg);
}

.tone-arm:before,
.tone-arm:after {
  content: "";
  position: absolute;
  margin: auto;
  background: #b7b6b6;
}

.tone-arm:before {
  left: 0;
  right: 1px;
  top: 40px;
  width: 7px;
  height: 120px;
}

.tone-arm:after {
  left: -2px;
  right: 0;
  bottom: 0;
  top: 300px;
  width: 20px;
  height: 30px;
  border-radius: 20%;
}

Rendons le disque plus réaliste

Pour rendre la scène plus réaliste, nous allons ajouter un effet de lumière en créant des reflets et des rainures sur le disque.

<body>
  <div class="gradient-background">
    <div class="turntable">
      <div class="disc">
        <div class="disc-reflection-left">
        </div>
        <div class="disc-reflection-right">
        </div>
        <div class="disc-groove">
        <div class="label">
          <div class="disc-font disc-title">
            CSS rocks!
          </div>
          <div class="disc-font disc-group">
            wubba lubba dub dub
          </div>
        </div>
      </div>
      <div class="buttons">
      </div>
      <div class="tone-arm oscillating">
      </div>
    </div>
  </div>
</body>
.disc-reflection-left, .disc-reflection-right {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  position: absolute;
}

.disc-reflection-left {
  background-color: #36495d;
  clip-path: polygon(50% 50%, 0 75%, 10% 100%);
}

.disc-reflection-right {
  background-color: #36495d;
  clip-path: polygon(90% 0, 50% 50%, 100% 25%);
}

.disc-groove {
  position: absolute;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  border-radius: 50%;
  width: 200px;
  height: 200px;
  border: solid 2px #2D3E4F;
}

.disc-groove:before, .disc-groove:after {
  position: absolute;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  border-radius: 50%;
  border: solid 2px #2D3E4F;
}

.disc-groove:before{
  content: "";
  width: 180px;
  height: 180px;
}

.disc-groove:after {
  content: "";
  width: 160px;
  height: 160px;
}

Les rainures sont tout simplement des cercles, avec une bordure, du même genre que ceux créés pour l’étiquette du disque.

Pour les reflets sur le disque nous utilisons la propriété clip-path pour laquelle nous définissons un polygone. clip-path permet de faire un rognage de la forme souhaitée. Ainsi nous pouvons définir une zone précise qui sera visible, le reste étant masqué. La propriété clip-path peut prendre comme valeur une fonction comme inset(), circle(), ellipse() ou polygon(). Nous utiliserons la fonction polygon() pour définir la position des sommets de triangles.

Pour manipuler facilement cette propriété, le site Clippy (CSS clip-path maker) propose différentes formes prédéfinies personnalisables.

Un triangle masqué avec la propriété clip-path.

Le triangle reflection-left créé via le site Clippy.

Ajoutons un peu d’animation

En ajoutant une animation, nous donnons une impression de mouvement. Avec les propriétés transform et opacity, il est facile de jouer sur la position, l’échelle, la rotation et l’opacité d’un élément.

Il est préférable d’utiliser les propriétés transform et opacity plutôt que de modifier directement les propriétés width, height, left, top, bottom, right (etc.) pour avoir des animations plus fluides et une feuille de style plus facile à lire.

<body>
  <div class="gradient-background">
    <div class="turntable">
      <div class="disc">
        <div class="disc-reflection-left">
        </div>
        <div class="disc-reflection-right">
        </div>
        <div class="disc-groove">
        </div>
        <div class="label spin">
          <div class="disc-font disc-title">
            CSS rocks!
          </div>
          <div class="disc-font disc-group">
            wubba lubba dub dub
          </div>
        </div>
      </div>
      <div class="buttons">
      </div>
      <div class="tone-arm oscillating">
      </div>
    </div>
  </div>
</body>
.spin {
  animation: spin 4s linear infinite;
}

.oscillating {
  animation: oscillating 2s linear infinite;
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

@keyframes oscillating {
  0% { transform: rotate(20deg); }
  30% { transform: rotate(20.5deg); }
  60% { transform: rotate(20deg); }
  90% { transform: rotate(20.5deg); }
  100% { transform: rotate(20deg); }
}

Nous utilisons ici la rotation pour l’animation spin faisant tourner le disque sur lui-même. Pour cela nous indiquons un état initial : une rotation de 0 degré (rotate(0deg)) et un état final : une rotation de 360 degrés (rotate(360deg)). Ces états sont définis dans des @keyframes. L’animation se fait ensuite par la propriété animation qui prend en paramètre plusieurs valeurs, nous renseignerons uniquement les quatre premières pour utiliser le keyframes défini pour une animation d’une durée de 4 secondes linéaire et que l’animation se répète de manière infini.

Plus d’infos sur l’utilisation des animations CSS sur MDN.

Vous devriez maintenant avoir le même résultat que celui affiché en début d’article. S’il existe des différences, je vous invite à aller voir le code disponible dans son intégralité.

Aller plus loin avec la compatibilité des différents navigateurs

Dans cette démonstration, nous n’avons pas utilisé les propriétés spécifiques à certains navigateurs pour assurer la compatibilité au plus grand nombre d’utilisateurs. La plupart des propriétés qui ne sont pas supportées nativement par certains navigateurs. Il faut utiliser des « préfixes propriétaires » qui sont des genres de mots-clés devant être préfixées d’un tiret et d’un code correspondant au moteur les exploitant :

  • -o- pour Opera
  • -moz- pour Gecko (Mozilla)
  • -webkit- pour Webkit (Chrome, Safari, Android…)
  • -ms- pour Microsoft (Internet Explorer)
  • -khtml- pour KHTML (Konqueror)

Ainsi pour assurer le rendu de la propriété transform sur d’anciennes versions de Firefox ou WebKit, il faudra ajouter des préfixes comme suit :

  • -moz-transform sous Gecko (de Firefox 3.5 à 15)
  • -webkit-transform sous WebKit (dans Safari jusqu’à iOS 8.1)

Il n’est pas nécessaire de supporter tous les navigateurs, mais de se fier plutôt aux utilisations réelles, comme par exemple les statistiques de StatCounter.

Pour les usages par version, recherchez sur le site Can I use une propriété, et le pourcentage d’utilisateurs par navigateur et par version est indiqué.

CSS nous offre une multitude de possibilités

Une « peinture » entièrement réalisée en CSS par la développeuse Diana Smith

Sans aller jusqu’à ce niveau de technicité (dans un premier temps), il faudra avant tout considérer la « construction » de votre code comme un ensemble de formes simples. Pour notre platine vinyle nous avons utilisé presqu’exclusivement des rectangles, cercles et triangles!

Cela vous rappellera peut être un jeu auquel vous jouiez enfant : le tangram.

Le jeu du tangram

Mais en assemblant toutes ces formes, vous pouvez créer tout un univers.

Des modèles en tangram

À vous de jouer !

4 commentaires sur cet article

  1. M

    Mickael, le mercredi 19 décembre 2018 à 20:49

    Top ce petit article! :)

  2. A

    Abass Ben cheik, le jeudi 20 décembre 2018 à 18:50

    Waaw j’adore 1000 fois

  3. W

    William, le jeudi 20 décembre 2018 à 21:16

    Vraiment intéressant cet article !

  4. A

    Aurélie, le mercredi 26 décembre 2018 à 11:46

    Génial ! Ca parait super simple, ca donne envie de se lancer ! Merci pour cet article !

Laisser un commentaire

Les commentaires sont modérés manuellement. Merci de respecter : la personne qui a écrit l'article, les autres participant(e)s à la discussion, et la langue française. Vous pouvez suivre les réponses par flux RSS.