Greboca  

Suport technique et veille technologique

Aujourd’hui, les grandes entreprises et administrations publiques hésitent entre continuer à utiliser des logiciels propriétaires ou basculer vers les Logiciels Libres. Pourtant, la plupart des logiciels libres sont capables de bien traiter les données issues des logiciels propriétaire, et parfois avec une meilleur compatibilité.

C’est alors la barrière de la prise en main qui fait peur, et pourtant...

Les logiciels libres

L’aspect « Logiciel Libre » permet une évolution rapide et une plus grande participation des utilisateurs. Les aides et tutoriels foisonnent sur Internet ou sont directement inclus dans le logiciel lui-même.

Enfin, les concepteurs sont plus proches des utilisateurs, ce qui rend les logiciels libres plus agréable à utiliser et conviviaux.

Grâce à la disponibilité des logiciels libres, vous trouverez facilement des services de support techniques et la licence n’est plus un frein à l’utilisation de ces logiciels par votre personnel.

Notre support technique concerne essentiellement les logiciels libres, que ce soit sous forme de services ponctuels ou de tutoriels.

Bidouilleux d’Web  -  La propriété background-clip et ses cas d'utilisation

 -  Février 2016 - 

Cet article est une traduction de The background-clip Property and its Use Cases, écrit par Ana Tudor et publié sur CSS Tricks. Ana Tudor réalise de nombreuses démos sur CSS sur CodePen et via Twitter et cet article est l’occasion d’en dire un peu plus sur la propriété background-clip. Merci beaucoup à marine, Banban, Thegennok et Ilphrin pour la traduction et à Théo et goofy pour la relecture.

background-clip fait partie de ces propriétés que je connais depuis des années, mais que j’ai rarement utilisées, peut-être une ou deux fois pour répondre à une question sur Stack Overflow, jusqu’à l’année dernière, où j’ai commencé à créer une collection de barres de défilement. Certains des designs que j’ai reproduits étaient un peu plus complexes et je n’avais qu’un élément disponible par barre : un élément input. Je ne pouvais donc pas utiliser les pseudo-éléments. Même si cela fonctionne dans certains navigateurs, le fait est que c’est à cause d’un bug et je ne voulais pas me baser là-dessus. En un mot comme en cent, j’ai fini par énormément utiliser les arrière-plans, les bordures et les ombres. J’ai beaucoup appris en faisant cela et cet article partage certaines de ces leçons.

Avant toutes choses, voyons ce à quoi correspond background-clip et ce qu’il fait.

Sur l’image suivante, nous avons un modèle des boîtes pour les éléments :

Le modèle de boites

Si le padding est fixé à 0, alors la boîte de padding (padding-box) a exactement la même taille que la boîte de contenu (content-box) et la limite du contenu coïncide avec la limite du padding.

Avec padding: 0

Si l’épaisseur de la bordure (border-width) est fixée à 0, la boîte de la bordure a la même taille que la boîte de padding et la limite de la bordure coïncide avec la limite du padding.

Avec border-width:0

Si, en même temps, le padding est fixé à 0 et l’épaisseur de la bordure à 0, alors les trois boîtes (contenu, padding et bordure) ont la même taille et les trois limites coïncident.

Avec padding: 0 et border-width: 0

Par défaut, les arrière-plans recouvrent toute la boîte de la bordure (ils sont même appliqués sous la bordure), mais la position de l’arrière-plan (background-position) et les tailles relatives pour background-size sont fixées par rapport à la boîte de padding.

Pour mieux comprendre tout ça, étudions un exemple. On s’intéresse à une boîte avec des dimensions aléatoires, on lui attribue un fond dégradé simple avec background-size: 50% 50% ainsi qu’une bordure en tirets (en utilisant une border-image) afin qu’on puisse voir à travers les tirets ce qui se passe sous la bordure.

Dans cette démo, nous pouvons voir que le fond dégradé occupe toute la boîte de bordure : on le voit sous la bordure en tirets. Nous n’avons pas indiqué de position pour le fond, il prend donc la valeur par défaut (0 0). Nous pouvons nous rendre compte que cette position dépend de la boîte de padding, car elle démarre du coin supérieur gauche (le point de coordonnées 0 0) de cette boîte. Nous pouvons également voir que la taille définie en pourcentage est relative à la taille de la boîte du padding.

Par défaut, le fond couvre toute la boîte de la bordure, mais démarre au coin supérieur gauche de la boîte du padding

Quand on règle la taille du fond (background-size) avec un gradient (sans image réelle), nous avons généralement besoin de 2 valeurs pour obtenir des résultats comparables avec les différents navigateurs. Si on ne met qu’une valeur, Firefox fixe la seconde à 100% (comme indiqué par la spécification), alors que tous les autres navigateurs choisissent, de façon incorrecte, une valeur égale à la première. L’absence de cette seconde valeur est comprise comme étant auto et, comme les gradients n’ont pas de dimensions ni de proportions intrinsèques, la valeur auto ne peut pas être déduite de celles-ci, elle doit donc être fixée à 100%. Ainsi, sauf dans le cas où nous souhaitons que les deux dimensions du fond soient 100%, nous devons utiliser 2 valeurs.

En utilisant une seule valeur pour la taille du fond, cela ne produit pas de résultats cohérents sur les différents navigateurs. À gauche : Firefox (qui, selon la spécification, fixe la seconde valeur à 100%) ; à droite : Chrome/Opera, Safari, IE/Edge (qui, de façon incorrecte, choisissent la seconde valeur égale à la première).

Nous pouvons décider que l’arrière-plan recouvre uniquement la boîte de padding ou la boîte de contenu grâce à background-clip. Le rognage (clipping) consiste à couper et à ne pas afficher ce qui est situé en dehors de la zone de rognage. Cette dernière correspond à la zone à l’intérieur du rectangle en pointillés ci-dessous.

illustration du rognage (_clipping_)

Par défaut, background-clip vaut border-box et la zone de rognage est donc la boîte de bordure, ainsi l’arrière-plan est également dessiné en dessous de la bordure.

background-clip: border-box

Avec background-clip: padding-box, la zone de rognage est la boîte de padding. Cela signifie que l’arrière-plan sera uniquement affiché à l’intérieur de la boîte de padding (il ne sera pas visible sous la bordure).

background-clip: padding-box

Enfin, avec background-clip: content-box, la zone de rognage correspond à la boîte de contenu et l’arrière-plan est seulement visible à l’intérieur de cette boîte.

background-clip: content-box

La démonstration ci-dessous illustre ces trois situations :

Il existe une autre propriété, appelée background-origin, qui définit à laquelle des trois boîtes se réfère background-position et background-size (quand la valeur de cette propriété est exprimée en pourcents).

Imaginons que nous avons un élément comme avant avec une bordure hachurée et cette fois un padding visible. Nous superposons une image réelle avec un dégradé d’arrière-plan. Les deux ont background-size: 50% 50% sans répétition. De plus, l’image a background-position: 100% 100% (on laisse le 0 0 par défaut pour le dégradé) :

background: linear-gradient(to right bottom, 
      #e18728, #be4c39, #9351a6, #4472b9), 
  url(tiger_lily.jpg) 100% 100%;
background-repeat: no-repeat;
background-size: 50% 50%;

La démonstration qui suit illustre ce qui se passe pour chacune des trois valeurs possibles de background-origin (border-box, padding-box, et content-box) :

Le couple de valeur 100% 100%, utilisé dans la déclaration background, correspond à 100% 100% de la boîte définie avec background-origin. Le couple 50% 50%, utilisé comme valeur de la propriété background-size, signifie que l’image devra prendre la moitié, en hauteur et en largeur, de la boîte indiquée par background-origin.

En utilisant la propriété raccourcie background, background-origin et background-clip peuvent être définies, dans cet ordre, à la fin de la déclaration. Étant donné qu’elles prennent chacune une valeur de boîte, si une seule valeur de boîte est fournie, les deux propriétés utiliseront cette boîte. Si deux boîtes sont définies, background-origin sera basée sur la première et background-clip utilisera la deuxième. Si aucune valeur de boîte n’est fournie, ces propriétés utiliseront les valeurs par défaut (padding-box pour background-origin et border-box pour background-clip).

Très bien ! Maintenant, voyons comment tirer parti de tout ça !

Espace transparent entre la bordure et l’arrière-plan

Certains se souviennent peut-être que nous pouvons avoir une bordure semi-transparente avec background-clip. Mais nous pouvons aussi ajouter de l’espace entre la bordure et la zone d’arrière-plan sans ajouter d’élément. Le moyen le plus simple consiste à avoir un padding en plus d’une bordure, et de mettre aussi background-clip à content-box. En faisant ça avec une propriété raccourcie et en utilisant uniquement une valeur de boîte, background-origin vaudra également content-box. Pas de problème pour ce cas, il n’y a pas d’effets indésirables.

border: solid .5em #be4c39;
padding: .5em;
background: #e18728 content-box;

Rogner l’arrière-plan avec content-box l’empêche de dépasser de la boîte de contenu. Au-delà, il n’y a pas d’arrière-plan, on peut donc voir ce qu’il y a derrière cet élément. Si on ajoute une bordure, on voit la bordure entre la limite de padding et la limite de bordure. Si le padding n’est pas de taille nulle, nous avons toujours une zone entre la limite de contenu et la limite de padding.

Mise en avant de la bordure, du padding et du contenu via les outils de développement.

Voici ce que ça donne en démo :

Pour rendre les choses plus intéressantes, on peut utiliser un filtre drop-shadow() pour donner un halo jaune à l’ensemble.

Rappels sur les préfixes : dans beaucoup de ressources, j’ai pu voir les préfixes -moz- et -ms- utilisés pour les filtres CSS. Erreur à éviter ! Les filtres CSS ne sont plus préfixés dans Firefox depuis leur implémentation (Firefox 34, automne 2014) et maintenant ils sont présents dans Edge avec une préférence, sans préfixe. Les filtres CSS n’ont donc jamais eu besoin d’être préfixés par -moz- ou -ms-. Ne les ajoutez pas dans vos feuilles de style, ils ne font qu’embrouiller le code CSS.

On peut aussi obtenir un effet sympa en utilisant un gradient pour chacune des propriétés background-image et border-image. On prendra un gradient qui commence par du orange-rouge en haut puis qui s’atténue vers une transparence complète. Vu que seules les couleurs de base sont différentes et que le gradient appliqué sera le même, on crée une fonction Sass :

@function fade($c) {
  return linear-gradient($c, rgba($c, 0) 80%);
}
div {
  border: solid 0.125em;
  border-image: fade(#be4c39) 1;
  padding: 0.125em;
  background: fade(#e18728) content-box;
}

Voici le résultat dans cette démo :

Cette approche, qui consiste à utiliser le padding pour créer un espace entre l’arrière-plan et la bordure, n’est pas la meilleure, à moins d’avoir seulement un court texte au milieu. S’il y a plus de texte, l’effet n’est pas très joli.

Le problème qu’on rencontre alors est qu’on ne peut pas ajouter d’espace entre le bord de la zone orange et le texte car le padding est déjà utilisé pour l’espace transparent. On pourrait ajouter un autre élément… ou on pourrait utiliser box-shadow.

box-shadow est une propriété qui peut prendre 2, 3 ou 4 valeurs de longueurs. La première correspond au décalage horizontal de l’ombre vers la droite, la deuxième valeur correspond au décalage vertical de l’ombre vers le bas, la troisième correspond au rayon de flou (qui détermine la force du flou autour de l’ombre) et la quatrième correspond au rayon d’étalement (qui caractérise la façon dont l’ombre s’étale dans l’ensemble des directions).

Voici une démo interactive pour jouer avec ces valeurs, cliquez sur chacune d’elle et utilisez le curseur qui s’affiche.

Le rayon de flou doit toujours être supérieur ou égal à zéro mais les décalages et le rayon d’étalement peuvent être négatifs. Un décalage négatif permettra simplement de décaler l’ombre dans la direction opposée (vers la gauche et/ou vers le haut) et un rayon d’étalement négatif signifie que l’ombre rétrécira.

Autre chose à noter ici (ça nous servira dans ce cas) : la zone dessinée avec box-shadow n’est jamais visible sous l’espace occupé par border-box, même quand cet espace est à moitié transparent.

Si on garde les décalages et le rayon de flou nuls mais qu’on indique un rayon d’étalement positif, on obtiendra quelque chose de semblable à une deuxième bordure de largeur égale dans chaque direction à partir des limites de la bordure réelle et vers l’extérieur.

Émuler une bordure avec box-shadow

Il n’est pas nécessaire que la bordure ait la même épaisseur dans les quatre directions. On peut obtenir des épaisseurs différentes en jouant sur les valeurs des décalages et sur celle du rayon d’étalement. La seule contrainte qu’il faut respecter est la suivante : la somme des largeurs des bordures verticales doit être égale à celle des largeurs des bordures verticales. En utilisant plusieurs ombres, on pourrait lever cette restriction si on n’a pas besoin que la bordure soit semi-transparente, mais cette solution serait plus une source de complexité que de simplicité.

Revenons à notre démo, on utilise l’étalement de box-shadow pour émuler une bordure et la vraie bordure est utilisée pour créer l’espace transparent, on définit background-clip avec padding-box et on laisse le padding remplir son rôle.

border: solid 1em transparent;
padding: 1em;
box-shadow: 0 0 0 1em #be4c39;
background: #e18728 padding-box;

Identifier les zones de bordure, padding et contenu grâce aux outils de développement.

Voici ce que ça donne en démo :

On peut aussi simuler une bordure avec une ombre incrustée. Dans ce cas, elle commence à la limite entre la zone de padding et la zone de bordure et elle s’étend vers l’intérieur autant que le définit le rayon d’étalement :

Émulation d'un deuxième bordure avec une box-shadow incrustée

On peut conjuguer plusieurs ombres box-shadow et on peut donc émuler plusieurs bordures de cette façon. Prenons un exemple avec deux bordures dont l’une est incrustée. Si la vraie bordure de l’élément est non nulle et transparente et que background-clip vaut padding-box, on peut tricher et créer une double bordure avec une zone transparente (celle occupée par la vraie zone de bordure) entre les composants intérieurs et extérieurs. Attention, pour compenser l’espace pris par l’ombre incrustée, il faudra augmenter le padding.

border: solid 1em transparent;
padding: 2em; // on passe de 1em à 2em pour compenser la box-shadow "intérieure" 
  0 0 0 0.25em #be4c39 /* bordure extérieure */,
  inset 0 0 0 1em #be4c39 /* bordure intérieure */;
background: #e18728 padding-box;

Vous pouvez utiliser ce Pen pour voir tout ça en action, avec un filtre drop-shadow() qui ajoute un effet de brillance.

Dessiner une cible aux bords lisses avec un seul élément (pas de pseudo-élément)

Imaginons qu’on veuille dessiner une cible comme celle-ci en ayant comme contrainte de ne devoir utiliser qu’un seul élément et aucun pseudo-élément.

La cible qu'on souhaite dessiner avec CSS

Une première approche consisterait à utiliser un gradient radial qu’on répète (repeating-radial-gradient) avec une structure comme celle-ci :

Illustration de la structure de la cible

Une demi-cible correspondrait donc a 9 unités. Les axes vertical et horizontal de la cible seraient donc composés de 18 unités. Le gradient radial est noir pour la première unité, transparent jusqu’à la troisième, noir à nouveau, etc. On a une répétition à l’œuvre. Seulement, on a uniquement une unité entre 0 et 1. La première fois, on a une région noire et la deuxième fois également. Ensuite on passe de 3 à 5, ce qui donne deux unités ! Pour régler ça, on ne devrait pas commencer à partir de 0 mais à partir de -1, n’est-ce pas ? D’après la spécification, ceci devrait fonctionner :

$unit: 1em;
background: repeating-radial-gradient(
  #000 (-$unit), #000 $unit, 
  transparent $unit, transparent 3*$unit
);

Voici le Pen qui illustre cette idée :

Seulement voilà, IE a une opinion différente…

utiliser repeating-radial-gradient avec un premier point négatif : résultat attendu (à gauche) et IE (à droite)

Heureusement, ça a été corrigé avec Edge, mais si on doit supporter IE, ça continue de poser problème. Pour ça, plutôt que d’utiliser un gradient répété, on peut utiliser un gradient radial classique car il n’y a pas tant de cercles à dessiner. Au final, on a plus de code, mais ça reste acceptable.

background: radial-gradient(
  #000 $unit, transparent 0, 
  transparent 3*$unit, #000 0, 
  #000 5*$unit, transparent 0, 
  transparent 7*$unit, #000 0, 
  #000 9*$unit, transparent 0
);

Voici la démo :

En utilisant cette méthode, on a bien la même distribution sur les différents navigateurs. Cependant, on a un autre problème : les bords des cercles sont loin d’être lisses comme ce qu’on obtenait sous IE, notamment avec Firefox et Chrome !

original (en haut à gauche), IE/Edge (en haut à droite), Firefox (en bas à gauche), Chrome (en bas à droite)

On pourrait utiliser cette astuce pour créer une transition moins abrupte :

background: radial-gradient(
  #000 calc(#{$unit} - 1px), 
  transparent $unit, 
  transparent calc(#{3*$unit} - 1px), 
  #000 3*$unit, 
  #000 calc(#{5*$unit} - 1px), 
  transparent 5*$unit, 
  transparent calc(#{7*$unit} - 1px), 
  #000 7*$unit, 
  #000 calc(#{9*$unit} - 1px), 
  transparent 9*$unit
);

Le résultat sur la démo :

Pour IE, c’est mieux (on avait déjà un résultat satisfaisant), pour Firefox également mais le problème persiste sous Chrome.

original (en haut à gauche), IE/Edge (en haut à droite), Firefox (en bas à gauche), Chrome (en bas à droite)

Peut-être que les gradients radiaux ne sont pas la meilleure solution pour ce problème. Pourrait-on adapter la solution utilisée précédemment utilisée, avec background-clip et box-shadow ? On peut utiliser une box-shadow extérieure pour le cercle extérieur et une autre incrustée pour le cercle intérieur. L’espace intermédiaire est dessiné avec la bordure transparente. On définit également background-clip sur content-box et on définit un padding suffisant pour qu’on ait une zone transparente entre le disque central et le cercle intérieur.

border: solid 2*$unit transparent;
padding: 4*$unit;
width: 2*$unit;
height: 2*$unit;
border-radius: 50%;
box-shadow: 
  0 0 0 2*$unit #000, 
  inset 0 0 0 2*$unit #000;
background: #000 content-box;

Voilà le résultat dans le Pen, aucun bord rugueux et aucun problème !

Des contrôles réalistes

Cette idée m’est venue lorsque j’ai dû mettre en forme les pistes, les curseurs et, pour les navigateurs non-Webkit, les barres de progression. Pour ces composants, les navigateurs fournissent un ensemble de pseudo-éléments.

Pour les pistes, on peut utiliser -webkit-slider-runnable-track, -moz-range-track et -ms-track. Pour les curseurs, on a -webkit-slider-thumb, -moz-range-thumb et -ms-thumb. Enfin, pour les barres de progression, il y a -moz-range-progress, -ms-fill-lower (les deux sont à gauche du curseur) et -ms-fill-upper (à droite du curseur). Les navigateurs basés sur Webkit ne proposent pas de pseudo-élément qui permettrait de mettre en forme indépendamment la partie avant le curseur et la partie après le curseur.

Les noms des pseudo-éléments sont incohérents et laids mais ce qui est encore plus moche, c’est de ne pas pouvoir lister toutes les versions de navigateurs en même temps pour les mettre en forme de façon uniforme. Quelque chose comme ceci ne fonctionnera pas :

input[type='range']::-webkit-slider-thumb, 
input[type='range']::-moz-range-thumb, 
input[type='range']::-ms-thumb { /* les styles ici */  }

On doit toujours l’écrire de cette manière :

input[type='range']::-webkit-slider-thumb { /* les styles ici */ }
input[type='range']::-moz-range-thumb { /* les styles ici */ }
input[type='range']::-ms-thumb { /* les styles ici */ }

Cela donne l’impression d’un style de code très LOURD et c’est vraiment souvent le cas ; cela dit, vu le nombre d’incohérences des navigateurs pour ce qui est des curseurs, c’est parfois utile de pouvoir harmoniser les choses. Pour répondre à ce problème, j’ai utilisé un mixin thumb() et je lui fournis des arguments pour gérer les incohérences :

@mixin thumb($flag: false) {
  /* styles */

  @if $flag {  /* plus de styles */ }}

input[type='range'] {
  &::-webkit-slider-thumb { @include thumb(true); }
  &::-moz-range-thumb { @include thumb(); } 
  &::-ms-thumb { @include thumb(); }}

Revenons à la façon dont on peut mettre en forme les choses. On peut ajouter des pseudo-éléments à ces composants seulement pour Chrome et Opera, ce qui signifie que, pour reproduire leur apparence, on doit s’en approcher le plus possible sans avoir recours aux pseudo-éléments. Il faut donc gérer les arrière-plans, les bordures, les ombres, les filtres sur l’élément.

Voyons quelques exemples !

Un bouton à l’aspect plastique

Pour prendre un exemple visuel, dites-vous qu’on souhaite obtenir un curseur comme celui-ci

Un curseur plastique

À première vue, pour ce type de sélecteur, il devrait suffire d’ajouter un dégradé avec background, un autre avec border-image, il n’y a plus qu’à ajouter une box-shadow et c’est bon :

border: solid 0.375em;
border-image: linear-gradient(#fdfdfd, #c4c4c4) 1;
box-shadow: 0 0.375em 0.5em -0.125em #808080;
background: linear-gradient(#c5c5c5, #efefef);

Effectivement, ça fonctionne (en utilisant un élément button à la place du curseur pour simplifier les choses) :

Mais notre sélecteur est rond et non carré, il manque donc seulement un border-radius: 50% et c’est bon ? Eh bien… ça ne fonctionne pas parce que nous utilisons border-image ce qui fait que border-radius est ignoré sur l’élément lui-même, même si, chose étrange, il reste appliqué sur box-shadow s’il y en a un.

Alors que devons-nous faire ? Utiliser background-clip ! Premièrement on donne un padding non nul à l’élément, pas de bordure et on le rend rond avec border-radius: 50%. Ensuite on superpose les arrière-plans dégradés, celui du dessus étant ajusté à la content-box (notez que le clipping est inclus dans la propriété raccourcie background). Enfin on ajoute deux box-shadows, la première étant plus sombre pour créer l’ombre sous le curseur et la seconde étant incrustée et qui devrait foncer un peu le bas et les côtés de la partie externe du bouton.

border: none; /* pour border-box ≡ padding-box */
padding: .375em;
border-radius: 50%;
box-shadow: 0 .375em .5em -.125em #808080, 
      inset 0 -.25em .5em -.125em #bbb;
background: 
  linear-gradient(#c5c5c5, #efefef) content-box, 
  linear-gradient(#fdfdfd, #c4c4c4);

Le résultat final est visible sur ce Pen :

Un curseur mat

L’objectif à atteindre est de réaliser ce type de curseur:

Un curseur avec un bouton mat

Cet exemple ressemble au précédent mais on a un contour plus clair en haut et une ombre au milieu du cercle. Si on utilise box-shadow pour la partie plus claire du contour, on n’aurait plus un rond à moins de réduire la hauteur pour compenser l’ombre. Cela signifie qu’il faudra calculer comment positionner le cercle intérieur. Si on utilise une ombre incrustée, on ne pourra pas en tirer parti pour l’ombre sombre de la partie intérieure. Cependant, on pourrait utiliser une stratégie analogue au cas précédent, sauf qu’on a radial-gradient sur les autres arrière-plans utilisés. La taille réelle de background-size pour le gradient est supérieure à la boîte de contenu et on peut donc la décaler vers le bas sans que le bord haut atteigne la limite du contenu.

border: none; /* pour border-box ≡ padding-box */
padding: .625em;
width: 1.75em;
height: 1.75em;
border-radius: 50%;
box-shadow: 
  0 1px .125em #444 /* ombre inférieure */, 
  inset 0 1px .125em #fff /* haut plus clair */;
background: 
  /* effet pour l'ombre intérieure */
  radial-gradient(transparent 35%, #444) 
    50% calc(50% + .125em) content-box, 

  /* arrière-plan intérieur */
  linear-gradient(#bbb, #bbb) content-box, 

  /* arrière-plan extérieur */
  linear-gradient(#d0d3d5, #d2d5d7);
background-size: 
  175% 175% /* on rend le radial-gradient plus grand */, 
  100% 100%, 100% 100%;

Voici la démonstration du résultat obtenu

Un contrôle en 3D

Prenons par exemple le bouton de ce curseur :

Un curseur avec un bouton en 3D

Celui-ci est plus complexe et il faut que les trois boîtes (contenu, padding, bordure) soient différentes afin qu’on puisse empiler différents arrière-plans et utiliser background-clip pour obtenir l’effet voulu.

Ainsi pour la partie principale du curseur, on applique un arrière-plan en gradient, fixé par rapport à la boîte de contenu. En dessous on applique un autre arrière-plan, fixé par rapport à la boîte de padding. Encore en dessous, on en place un par rapport à la boîte de bordure avec un gradient linéaire. On utilise également box-shadow avec inset pour appuyer sur la limite entre la boîte de padding et la boîte de bordure.

border: solid .25em transparent;
padding: .25em;
border-radius: 1.375em;
box-shadow: 
  inset 0 1px 1px rgba(#f7f7f7, .875) /* haut */, 
  inset 0 -1px 1px rgba(#bbb, .75) /* bas */;
background: 
  linear-gradient(#9ea1a6, #fdfdfe) content-box, 
  linear-gradient(#fff, #9c9fa4) padding-box, 
  linear-gradient(#eee, #a4a7ab) border-box;

Voici un démonstration du résultat qu’on obtient :

Maintenant, voyons la partie ronde. Pour Blink, cette fois, j’ai dû me résoudre à utiliser un pseudo-élément. En revanche pour les autres navigateurs, j’ai pu obtenir un résultat satisfaisant en appliquant deux gradients radiaux sur les gradients linéaires :

On pourrait encore aller plus loin pour obtenir un meilleur rendu en ajoutant d’autres gradients ou en utilisant background-blend-mode mais je n’ai pas suffisamment la fibre artistique pour ça. Avec un pseudo-élément, c’est beaucoup plus simple d’obtenir le résultat voulu : on fixe sa position et sa taille puis on l’arrondit avec border-radius: 50%. Ensuite on lui applique un padding, aucune bordure et on utilise deux gradients pour l’arrière-plan dont le plus haut est un gradient radial fixé à la boîte de contenu.

padding: .125em;
background: 
  radial-gradient(circle at 50% 10%, 
      #f7f8fa, #9a9b9f) content-box, 
  linear-gradient(#ddd, #bbb);

Voici le résultat qu’on obtient avec cette méthode :

Pour le curseur réel, j’ai utilisé le même arrière-plan avec les gradients radiaux sur le dessus et j’ai ajouté le pseudo-élément pour Blink sur ces gradients. J’ai utilisé cette méthode car, pour Safari, les styles liés aux curseurs sont appliqués avec ::-webkit-slider-thumb mais Safari ne prend pas en charge les pseudo-éléments sur le curseur ou la piste. Si je devais retirer ces méthodes de contournement aux styles appliqués à ::-webkit-slider-thumb, Safari n’afficherait pas du tout la partie ronde.

Une illusion de profondeur

La piste utilisée pour ce curseur illustre bien l’idée voulue :

Un curseur sur une piste avec une impression de profondeur

Comme avant, pour l’élément, on donne une bordure transparente non nulle, un padding et des arrière-plans fixés sur différentes boîtes avec background-clip (celui avec content-box doit être au-dessus de celui avec pading-box qui doit être au-dessus de celui avec border-box). Dans ce cas, on utilise un gradient linéaire plus clair pour couvrir la zone de bordure, un autre plus sombre et quelques gradients radiaux qu’on réduit et qu’on ne répète pas afin d’assombrir la zone de remplissage et on utilise un gradient encore plus foncé pour la zone de contenu. Ensuite, on définit un border-radius au moins égal à la moitié de la zone de contenu plus deux fois le padding plus deux fois la bordure. On ajoute également une ombre box-shadow avec inset pour mettre en avant la limite du padding.

border: solid .375em transparent;
padding: 1em 3em;
width: 15.75em;
height: 1.75em;
border-radius: 2.25em;
background: 
  linear-gradient(#090909, #090909) content-box, 
  radial-gradient(at 5% 40%, #0b0b0b, transparent 70%) 
    no-repeat 0 35% padding-box /* gauche */, 
  radial-gradient(at 95% 40%, #111, transparent 70%) 
    no-repeat 100% 35% padding-box /* droit */, 
  linear-gradient(90deg, #3a3a3a, #161616) padding-box,
  linear-gradient(90deg, #2b2d2c, #2a2c2b) border-box;
background-size: 100%, 9em 4.5em, 4.5em 4.5em, 100%, 100%;

Seulement voilà, on a un problème :

En effet, vu le fonctionnement de border-radius (le rayon pour la zone de contenu est celui qu’on définit moins border-width moins le padding, ce qui est négatif), il n’y a pas d’arrondi pour la zone de contenu. Réglons le problème en émulant la forme souhaitée avec des gradients radiaux et linéaires pour la zone de contenu.

Pour ce faire, on commence par s’assurer que la largeur de la zone de contenu est un multiple de sa hauteur (ici 15.75em = 9 x 1.75em). On applique un gradient linéaire sans répétition, positionné au milieu, qui couvre toute la hauteur de la zone de contenu mais qui laisse des espaces équivalents à la moitié de la hauteur de la zone de contenu aux extrémités droite et gauche. Là-dessus, on ajoute un gradient radial pour lequel background-size est égal à hauteur de la zone de contenu, horizontalement et verticalement.

Des contrôles métalliques

Comment obtenir un bouton comme celui-ci ?

Un contrôle métallique

C’est un peu compliqué à réaliser donc décomposons le problème étape par étape. Pour commencer, on crée un bouton circulaire avec une largeur égale à la hauteur et en utilisant border-radius: 50%. Ensuite, on s’assure que box-sizing vaut border-box afin que le padding aille vers l’intérieur (afin qu’il soit soustrait aux dimensions que nous avons définies). Ensuite, on crée un bordure transparente et un padding. Voilà ce que ça donne :

$d-btn: 27em; /* le diamètre */
$bw: 1.5em;   /* la largeur de la bordure */

button {
  box-sizing: border-box;
  border: solid $bw transparent;
  padding: 1.5em;
  width: $d-btn; height: $d-btn;
  border-radius: 50%;
}

Ça ne ressemble pas encore à grand-chose car l’effet est principalement accompli avec deux propriétés encore absentes : box-shadow et background.

Avant d’aller plus loin, décomposons l’ensemble :

Décomposition du controle métallique

En partant de l’extérieur vers l’intérieur, on a :

  • Un anneau avec des diodes
  • Un anneau fin
  • Une zone perforée (qui contient le halo bleu autour de la zone intérieure
  • Une partie centrale ronde.

L’anneau extérieur sera la zone de la bordure, la zone centrale sera la zone de contenu et toute la partie intermédiaire (l’anneau intérieure et la partie perforée) sera la zone de padding. On crée l’anneau intérieur avec des ombres box-shadow en inset.

box-shadow: 
  /* Un ombre discrète pour séparer l'anneau extérieur */
  inset 0 0 1px #666, 

  /* La zone haute plus sombre */
  inset 0 1px .125em #8b8b8b, 
  inset 0 2px .25em #a4a2a3, 

  /* La zone basse plus sombre */
  inset 0 -1px .125em #8b8b8b, 
  inset 0 -2px .25em #a4a2a3, 

  /* La bande circulaire pour l'anneau intérieur */
  inset 0 0 0 .375em #cdcdcd;

On ajoute deux autres ombres extérieures, une première pour la marque avant l’anneau extérieur et une seconde pour l’ombre discrète sous le contrôle, on a donc :

box-shadow: 
  0 -1px 1px #eee, 
  0 2px 2px #1d1d1d, 
  inset 0 0 1px #666, 
  inset 0 1px .125em #8b8b8b, 
  inset 0 2px .25em #a4a2a3, 
  inset 0 -1px .125em #8b8b8b, 
  inset 0 -2px .25em #a4a2a3, 
  inset 0 0 0 .375em #cdcdcd;

On n’y est pas encore tout à fait mais ça avance :

On doit maintenant ajouter trois types d’arrière-plan en partant du haut vers le bas : ceux restreints à la boîte content-box (pour la zone centrale), ceux restreints à la boîte de padding (pour la zone perforée et le halo bleu) et ceux restreints à la boîte de bordure (pour créer l’anneau extérieur et les diodes).

On commence par la zone centrale où on a des lignes circulaires qui sont créées avec les trois gradients radiaux empilés qui créent un effet de texture avec cet empilement et qui permettent d’obtenir des réflexions coniques avec un gradient conique. Aujourd’hui, les gradients coniques ne sont pas pris en charge par tous les navigateurs et il faudra donc utiliser une prothèse (polyfill).

background:
  /* ======= content-box ======= */
  /* les lignes circulaires - 13, 19, 23 sont premiers */
  repeating-radial-gradient(
      rgba(#e4e4e4, 0) 0, 
      rgba(#e4e4e4, 0) 23px, 
      rgba(#e4e4e4, .05) 25px, 
      rgba(#e4e4e4, 0) 27px) content-box, 
  repeating-radial-gradient(
      rgba(#a6a6a6, 0) 0, 
      rgba(#a6a6a6, 0) 13px, 
      rgba(#a6a6a6, .05) 15px, 
      rgba(#a6a6a6, 0) 17px) content-box, 
  repeating-radial-gradient(
      rgba(#8b8b8b, 0) 0, 
      rgba(#8b8b8b, 0) 19px, 
      rgba(#8b8b8b, .05) 21px, 
      rgba(#8b8b8b, 0) 23px) content-box, 
  /* réflexions coniques */
  conic-gradient(/* des variations aléatoires sur des tons gris */
      #cdcdcd, #9d9d9d, #808080, 
      #bcbcbc, #c4c4c4, #e6e6e6, 
      #dddddd, #a1a1a1, #7f7f7f, 
      #8b8b8b, #bfbfbf, #e3e3e3, 
      #d2d2d2, #a6a6a6, #858585, 
      #8d8d8d, #c0c0c0, #e5e5e5, 
      #d6d6d6, #9e9e9e, #828282, 
      #8f8f8f, #bdbdbd, #e3e3e3, #cdcdcd) 
    content-box;

Ça commence à ressembler à quelque chose !

Passons à la partie perforée. Le halo bleu est simplement un gradient radial transparent à l’extérieur. Les perforations utilisées sont tirées du motif « fibre de carbone » provenant de la galerie construite par Lea Verou il y a cinq ans.

$d-hole: 1.25em; /* le diamètre d'une perforation */
$r-hole: .5*$d-hole; /* le rayon d'une perforation */
background: 
  /* ======= padding-box ======= */
  /* le halo bleu */
  radial-gradient(
      #00d7ff 53%, transparent 65%) padding-box, 
  /* les trous */
  radial-gradient(
      #272727 20%, transparent 25%) 
    0 0 / #{$d-hole} #{$d-hole} 
    padding-box,
  radial-gradient(
      #272727 20%, transparent 25%) 
    $r-hole $r-hole / #{$d-hole} #{$d-hole} 
    padding-box,
  radial-gradient(#444 20%, transparent 28%) 
    0 .125em / #{$d-hole} #{$d-hole} 
    padding-box,
  radial-gradient(#444 20%, #3d3d3d 28%) 
    #{$r-hole} #{$r-hole + .125em} / #{$d-hole} #{$d-hole} 
    padding-box

On s’approche d’un résultat satisfaisant :

Pour créer l’anneau extérieur, on utilise simplement un gradient conique :

conic-gradient(
  #b5b5b5, #8d8d8d, #838383, 
  #ababab, #d7d7d7, #e3e3e3, 
  #aeaeae, #8f8f8f, #878787, 
  #acacac, #d7d7d7, #dddddd, 
  #b8b8b8, #8e8e8e, #848484, 
  #a6a6a6, #d8d8d8, #e3e3e3, 
  #8e8e8e, #868686, #a8a8a8, 
  #d5d5d5, #dedede, #b5b5b5) border-box;

Et voilà un contrôle métallique !

Il n’y a pas encore de diode, réglons ce problème !

Chaque diode est composée de deux gradients radiaux empilés. Celui du dessus modélise la diode et celui du dessous, légèrement décalé, crée une légère marque sous la diode afin d’obtenir le même effet que pour les trous de la zone perforée. Le gradient du dessous est toujours le même, en revanche, celui du dessus varie selon l’état de la diode (allumée ou non). On prend la variable $k pour indiquer le nombre de diodes allumées et on utilise un gradient bleu pour celles-ci et un gradient gris pour les autres.

On a 24 diodes positionnées sur un cercle qui est au milieu de la zone de bordure. Le rayon du cercle est égal au rayon du contrôle auquel on a soustrait la moitié de la largeur de la bordure.

On utilisera Sass pour créer tous ces gradients. Pour commencer on crée une liste vide pour les gradients et à chaque itération de la boucle, on ajoute deux gradients à la liste. Les positions sont calculées afin qu’elles soient sur le cercle précédent. Le premier gradient dépend de l’indice de la boucle et le deuxième est toujours le même (seule sa position change).

$d-btn: 27em;
$bw: 1.5em;
$r-pos: .5*($d-btn - $bw);
$n-leds: 24;
$ba-led: 360deg/$n-leds;
$d-led: 1em;
$r-led: .5*$d-led;
$k: 7;$leds: ();
@for $i from 0 to $n-leds {
  $a: $i*$ba-led - 90deg;
  $x: .5*$d-btn + $r-pos*cos($a) - $r-led;
  $y: .5*$d-btn + $r-pos*sin($a) - $r-led;
  $leds: $leds, 
    if($i < $k, 
      (radial-gradient(circle, #01d6ff, 
          #178b98 .5*$r-led, 
          rgba(#01d6ff, .35) .7*$r-led, 
          rgba(#01d6ff, 0) 1.3*$r-led) no-repeat 
        #{$x - $r-led} #{$y - $r-led} / 
        #{2*$d-led} #{2*$d-led} border-box), 
      (radial-gradient(circle, #898989, 
          #4d4d4d .5*$r-led, #999 .65*$r-led, 
          rgba(#999, 0) .7*$r-led) no-repeat 
        $x $y / #{$d-led} #{$d-led} border-box)
    ), 
    radial-gradient(circle, 
        rgba(#e8e8e8, .5) .5*$r-led, 
        rgba(#e8e8e8, 0) .7*$r-led) no-repeat 
      $x ($y + .125em) / #{$d-led} #{$d-led} 
      border-box;
}

Voici le résultat final sur CodePen  :

Afficher des ombres sur un plan perpendiculaire

Prenons le cas où des contrôles sont affichés sur le plan, vertical, de l’écran et pour lesquels on voudrait un ombre portée sur le plan horizontal en dessous, quelque chose qui ressemble à cette image :

Des contrôles avec une ombre portant sur un plan horizontal en dessous

On souhaite recréer cet effet avec un seul élément et sans pseudo-élément. Là aussi, on peut empiler différents arrière-plans avec différentes valeurs de background-clip et background-origin. On crée le bouton avec deux arrière-plans, le premier en haut, attaché à la boîte de contenu et celui du dessous, attaché à la boîte de padding, pour créer l’ombre on utilise un arrière-plan radial-gradient() avec background-clip et background-origin fixés à border-box pour créer l’ombre.

La mise en forme de base est obtenue de façon semblable au contrôle de la section précédente :

$l: 6.25em;
$bw: .1*$l;
border: solid $bw transparent;
padding: 3px;
width: $l;
height: $l;
border-radius: 1.75*$bw;

On ajoute un bordure épaisse transparente tout autour afin d’avoir suffisamment d’espace pour recréer l’ombre dans la partie basse de la zone de bordure. On applique cela à toutes les bordures (pas uniquement à celle du bas) pour obtenir le même effet d’arrondi symétrique sur tous les coins de la boîte de padding (s’il vous faut des rappels sur le sujet, n’hésitez pas à consulter l’excellente conférence de Lea Verou sur border-radius).

Le premier arrière-plan, en partant du dessus, est un gradient conique qui crée des réflexions métalliques coniques. Celui-ci est rattaché à la boîte de contenu avec content-box. Juste en dessous, on a un gradient linéaire rattaché à la boîte de padding. On utilise trois ombres vers l’intérieur pour que ce deuxième arrière-plan semble moins plat (on ajoute une autre nuance toute autour avec un flou nul et qui s’étend légèrement, on rend le haut plus clair avec une ombre blanche semi-transparente et on assombrit la partie basse avec une ombre semi-transparente noire).

box-shadow: 
  inset 0 0 0 1px #eedc00, 
  inset 0  1px 2px rgba(#fff, .5), 
  inset 0 -1px 2px rgba(#000, .5);
background: 
  conic-gradient(
      #edc800, #e3b600, #f3cf00, #ffe800, 
      #ffe900, #ffeb00, #ffe000, #ebc500, 
      #e0b100, #f1cc00, #fcdc00, #ffe500, 
      #fad900, #eec200, #e7b900, #f7d300, 
      #ffe800, #ffe300, #f5d100, #e6b900, 
      #e3b600, #f4d000, #ffe400, #ebc600, 
      #e3b600, #f6d500, #ffe900, #ffe90a, 
      #edc800) content-box, 
  linear-gradient(#f6d600, #f6d600) padding-box

Voilà le bouton métallique (il n’y a pas encore l’ombre) :

Pour l’ombre, on applique un troisième couche d’arrière-plan pour laquelle background-clip et background-origin sont liés à border-box. L’arrière-plan est un gradient radial (dont la position est attachée en bas et au milieu) qu’on réduit verticalement afin qu’il puisse tenir dans la zone en bas de la boîte de bordure. Pour ça, on prend un taille environ égale à 75% de border-width.

radial-gradient(rgba(#787878, .9), rgba(#787878, 0) 70%) 
  50% bottom / 80% .75*$bw no-repeat border-box

Et voilà, vous pouvez utiliser ces boutons sur cette démo :

background-clip s’avère utile dans de nombreux cas ! Tout particulièrement lorsqu’on empile différents effets sur les bords des éléments.

Merci beaucoup à Ana Tudor qui a permis cette traduction !

par sphinx

Bidouilleux d’Web

Mise à jour à propos de la stratégie de localisation de MDN

 -  Décembre 2020 - 

Cet article est une traduction d’An update on MDN Web Docs’ localization strategy, écrit par Chris Mills. Si vous souhaitez contribuer aux (...)


Détails techniques sur la panne des modules complémentaires de Firefox

 -  Mai 2019 - 

Cet article est une traduction de Technical Details on the Recent Firefox Add-on Outage, écrit par Eric Rescorla, directeur technique de l’équipe (...)


Retrait de la confiance envers les certificats TLS de Symantec

 -  Août 2018 - 

Sites avec certificat émis par Symantecdans Chrome Canary à gaucheet Firefox Nightly à droiteLa plupart des grands éditeurs de navigateurs ont (...)


Une plongée illustrée dans les modules ECMAScript

 -  Avril 2018 - 

Cet article est une traduction de ES modules: A cartoon deep-dive, écrit par Lin Clark. Merci à goofy pour la relecture !Les modules ECMAScript (...)


Le projet Quantum de Mozilla, des ambitions et des précisions

 -  Décembre 2017 - 

Après un premier billet qui traçait les grandes lignes du projet et sa philosophie (Un saut quantique pour le Web) nous disposons maintenant d’un (...)