Bidouilleux d'Web - Mot-clé - CodePenLe blog technique de la communauté Mozilla francophone2022-02-04T14:07:43+01:00Communauté Mozilla francophoneurn:md5:935a9b6df47b29d6dc8c2ca47296b179DotclearLa propriété background-clip et ses cas d'utilisationurn:md5:5eb26d54d589ffa0e8c3f2aba83563ca2016-02-27T17:06:00+01:002016-02-29T08:04:56+01:00sphinxCSSbackground-clipCodePenCSScurseurs<p><em>Cet article est une traduction de <a href="https://css-tricks.com/the-backgound-clip-property-and-use-cases/">The <code>background-clip</code> Property and its Use Cases</a>, écrit par <a href="https://about.me/thebabydino">Ana Tudor</a> et publié sur <a href="https://css-tricks.com">CSS Tricks</a>. Ana Tudor réalise de nombreuses démos sur CSS sur <a href="http://codepen.io/">CodePen</a> et via <a href="https://twitter.com/anatudor">Twitter</a> et cet article est l’occasion d’en dire un peu plus sur la propriété <code>background-clip</code>. Merci beaucoup à marine, Banban, Thegennok et Ilphrin pour la traduction et à Théo et goofy pour la relecture.</em></p>
<p><code>background-clip</code> 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 <a href="http://codepen.io/collection/DgYaMj/">une collection de barres de défilement</a>. 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 <code>input</code>. Je ne pouvais donc pas utiliser les pseudo-éléments. Même si cela fonctionne dans certains navigateurs, <em>le fait est que c’est à cause d’un bug</em> et je ne voulais pas me baser là-dessus. En un mot comme en cent, j’ai fini par <em>énormément</em> 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.</p>
<p>Avant toutes choses, voyons ce à quoi correspond <a href="https://developer.mozilla.org/fr/docs/Web/CSS/background-clip">background-clip</a> et ce qu’il fait.</p>
<p>Sur l’image suivante, nous avons un modèle des boîtes pour les éléments :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-box-model_fr.png" alt="Le modèle de boites" /></p>
<p>Si le padding est fixé à <code>0</code>, alors la boîte de padding (<code>padding-box</code>) a exactement la même taille que la boîte de contenu (<code>content-box</code>) et la limite du contenu coïncide avec la limite du padding.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-with-padding0_fr.png" alt="Avec padding: 0" /></p>
<p>Si l’épaisseur de la bordure (<code>border-width</code>) est fixée à <code>0</code>, 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.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-border-width0_fr.png" alt="Avec border-width:0" /></p>
<p>Si, en même temps, le padding est fixé à <code>0</code> et l’épaisseur de la bordure à <code>0</code>, alors les trois boîtes (contenu, padding et bordure) ont la même taille et les trois limites coïncident.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-padding0-border-width0_fr.png" alt="Avec padding: 0 et border-width: 0" /></p>
<p>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 (<code>background-position</code>) et les tailles relatives pour <code>background-size</code> sont fixées par rapport à la boîte de padding.</p>
<p>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 <code>background-size: 50% 50%</code> ainsi qu’une bordure en tirets (en utilisant une <a href="https://developer.mozilla.org/fr/docs/Web/CSS/border-image"><code>border-image</code></a>) afin qu’on puisse voir à travers les tirets ce qui se passe sous la bordure.</p>
<iframe id="cp_embed_3e249da77243daaf72656f70fb757f66" src="//codepen.io/css-tricks/embed/3e249da77243daaf72656f70fb757f66?user=css-tricks&default-tab=result&slug-hash=3e249da77243daaf72656f70fb757f66&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>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 (<code>0 0</code>). 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 <code>0 0</code>) 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.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-background-cover-border-box_fr.png" alt="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" /></p>
<p>Quand on règle la taille du fond (<code>background-size</code>) 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 à <code>100%</code> (comme indiqué par <a href="https://drafts.csswg.org/css-backgrounds-3/#the-background-size">la spécification</a>), 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 <code>auto</code> et, comme les gradients n’ont pas de dimensions ni de proportions intrinsèques, la valeur <code>auto</code> ne peut pas être déduite de celles-ci, elle doit donc être fixée à <code>100%</code>. Ainsi, sauf dans le cas où nous souhaitons que les deux dimensions du fond soient <code>100%</code>, nous devons utiliser 2 valeurs.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-one-value-bgsize.png" alt="" /></p>
<p>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 à <code>100%</code>) ; à droite : Chrome/Opera, Safari, IE/Edge (qui, de façon incorrecte, choisissent la seconde valeur égale à la première).</p>
<p>Nous pouvons décider que l’arrière-plan recouvre uniquement la boîte de padding ou la boîte de contenu grâce à <code>background-clip</code>. Le rognage (<em>clipping</em>) 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.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-clipping-demo.gif" alt="illustration du rognage (_clipping_)" /></p>
<p>Par défaut, <code>background-clip</code> vaut <code>border-box</code> 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.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-bgclip-border-box.png" alt="background-clip: border-box" /></p>
<p>Avec <code>background-clip: padding-box</code>, 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).</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-bgclip-padding-box.png" alt="background-clip: padding-box" /></p>
<p>Enfin, avec <code>background-clip: content-box</code>, 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.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-bgclip-content-box.png" alt="background-clip: content-box" /></p>
<p>La démonstration ci-dessous illustre ces trois situations :</p>
<iframe id="cp_embed_7d5b8605cd83c27f645e03019b004cc4" src="//codepen.io/css-tricks/embed/7d5b8605cd83c27f645e03019b004cc4?user=css-tricks&default-tab=result&slug-hash=7d5b8605cd83c27f645e03019b004cc4&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>Il existe une autre propriété, appelée <code>background-origin</code>, qui définit à laquelle des trois boîtes se réfère <code>background-position</code> et <code>background-size</code> (quand la valeur de cette propriété est exprimée en pourcents).</p>
<p>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 <code>background-size: 50% 50%</code> sans répétition. De plus, l’image a <code>background-position: 100% 100%</code> (on laisse le <code>0 0</code> par défaut pour le dégradé) :</p>
<pre><code>background: linear-gradient(to right bottom,
#e18728, #be4c39, #9351a6, #4472b9),
url(tiger_lily.jpg) 100% 100%;
background-repeat: no-repeat;
background-size: 50% 50%;
</code></pre>
<p>La démonstration qui suit illustre ce qui se passe pour chacune des trois valeurs possibles de <code>background-origin</code> (<code>border-box</code>, <code>padding-box</code>, et <code>content-box</code>) :</p>
<iframe id="cp_embed_6bee9d25e11266a17508b44601ec220d" src="//codepen.io/css-tricks/embed/6bee9d25e11266a17508b44601ec220d?user=css-tricks&default-tab=result&slug-hash=6bee9d25e11266a17508b44601ec220d&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>Le couple de valeur <code>100% 100%</code>, utilisé dans la déclaration <code>background</code>, correspond à <code>100% 100%</code> de la boîte définie avec <code>background-origin</code>. Le couple <code>50% 50%</code>, utilisé comme valeur de la propriété <code>background-size</code>, signifie que l’image devra prendre la moitié, en hauteur et en largeur, de la boîte indiquée par <code>background-origin</code>.</p>
<p>En utilisant la propriété raccourcie <code>background</code>, <code>background-origin</code> et <code>background-clip</code> 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, <code>background-origin</code> sera basée sur la première et <code>background-clip</code> utilisera la deuxième. Si aucune valeur de boîte n’est fournie, ces propriétés utiliseront les valeurs par défaut (<code>padding-box</code> pour <code>background-origin</code> et <code>border-box</code> pour <code>background-clip</code>).</p>
<p>Très bien ! Maintenant, voyons comment tirer parti de tout ça !</p>
<h2>Espace transparent entre la bordure et l’arrière-plan</h2>
<p>Certains se souviennent peut-être que nous pouvons avoir <a href="https://css-tricks.com/transparent-borders-with-background-clip/">une bordure semi-transparente avec <code>background-clip</code></a>. 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 <code>background-clip</code> à <code>content-box</code>. En faisant ça avec une propriété raccourcie et en utilisant uniquement une valeur de boîte, <code>background-origin</code> vaudra également <code>content-box</code>. Pas de problème pour ce cas, il n’y a pas d’effets indésirables.</p>
<pre><code>border: solid .5em #be4c39;
padding: .5em;
background: #e18728 content-box;
</code></pre>
<p>Rogner l’arrière-plan avec <code>content-box</code> 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 <em>padding</em> et la limite de bordure. Si le <em>padding</em> n’est pas de taille nulle, nous avons toujours une zone entre la limite de contenu et la limite de <em>padding</em>.</p>
<p><img src="https://cdn.css-tricks.com/wp-content/uploads/2016/01/AT-highlighting-the-border.gif" alt="Mise en avant de la bordure, du padding et du contenu via les outils de développement." /></p>
<p>Voici ce que ça donne en démo :</p>
<iframe id="cp_embed_yYVNyz" src="//codepen.io/thebabydino/embed/yYVNyz?user=thebabydino&default-tab=result&slug-hash=yYVNyz&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>Pour rendre les choses plus intéressantes, on peut utiliser un filtre <code>drop-shadow()</code> pour donner un halo jaune à l’ensemble.</p>
<iframe id="cp_embed_gaLpaa" src="//codepen.io/thebabydino/embed/gaLpaa?user=thebabydino&default-tab=result&slug-hash=gaLpaa&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>Rappels sur les préfixes : dans beaucoup de ressources, j’ai pu voir les préfixes <code>-moz-</code> et <code>-ms-</code> 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 <a href="https://dev.windows.com/en-us/microsoft-edge/platform/status/filters">ils sont présents dans Edge avec une préférence</a>, sans préfixe. Les filtres CSS n’ont donc jamais eu besoin d’être préfixés par <code>-moz-</code> ou <code>-ms-</code>. Ne les ajoutez pas dans vos feuilles de style, ils ne font qu’embrouiller le code CSS.</p>
<p>On peut aussi obtenir un effet sympa en utilisant un gradient pour chacune des propriétés <code>background-image</code> et <code>border-image</code>. 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 :</p>
<pre><code>@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;
}
</code></pre>
<p>Voici le résultat dans cette démo :</p>
<iframe id="cp_embed_rOWVMK" src="//codepen.io/thebabydino/embed/rOWVMK?user=thebabydino&default-tab=result&slug-hash=rOWVMK&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>Cette approche, qui consiste à utiliser le <em>padding</em> 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.</p>
<iframe id="cp_embed_zvoGLb" src="//codepen.io/thebabydino/embed/zvoGLb?user=thebabydino&default-tab=result&slug-hash=zvoGLb&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>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 <em>padding</em> est déjà utilisé pour l’espace transparent. On pourrait ajouter un autre élément… ou on pourrait utiliser <code>box-shadow</code>.</p>
<p><code>box-shadow</code> est une propriété qui peut prendre 2, 3 ou 4 valeurs de longueurs. La première correspond au <strong>décalage horizontal</strong> de l’ombre vers la droite, la deuxième valeur correspond au <strong>décalage vertical</strong> de l’ombre vers le bas, la troisième correspond au <strong>rayon de flou</strong> (qui détermine la force du flou autour de l’ombre) et la quatrième correspond au <strong>rayon d’étalement</strong> (qui caractérise la façon dont l’ombre s’étale dans l’ensemble des directions).</p>
<p>Voici une <strong>démo interactive</strong> pour jouer avec ces valeurs, cliquez sur chacune d’elle et utilisez le curseur qui s’affiche.</p>
<iframe id="cp_embed_0733d9cb92ca4213466922d2ebc775a7" src="//codepen.io/css-tricks/embed/0733d9cb92ca4213466922d2ebc775a7?user=css-tricks&default-tab=result&slug-hash=0733d9cb92ca4213466922d2ebc775a7&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>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.</p>
<p>Autre chose à noter ici (ça nous servira dans ce cas) : la zone dessinée avec <code>box-shadow</code> n’est jamais visible sous l’espace occupé par <code>border-box</code>, même quand cet espace est à moitié transparent.</p>
<p>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.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-emulate-border.png" alt="Émuler une bordure avec box-shadow" /></p>
<p>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é.</p>
<p>Revenons à notre démo, on utilise l’étalement de <code>box-shadow</code> pour émuler une bordure et la vraie bordure est utilisée pour créer l’espace transparent, on définit <code>background-clip</code> avec <code>padding-box</code> et on laisse le <em>padding</em> remplir son rôle.</p>
<pre><code>border: solid 1em transparent;
padding: 1em;
box-shadow: 0 0 0 1em #be4c39;
background: #e18728 padding-box;
</code></pre>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-highlighting-bpc.gif" alt="Identifier les zones de bordure, padding et contenu grâce aux outils de développement." /></p>
<p>Voici ce que ça donne en démo :</p>
<iframe id="cp_embed_BoQNOZ" src="//codepen.io/thebabydino/embed/BoQNOZ?user=thebabydino&default-tab=result&slug-hash=BoQNOZ&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>On peut aussi simuler une bordure avec une ombre incrustée. Dans ce cas, elle commence à la limite entre la zone de <em>padding</em> et la zone de bordure et elle s’étend vers l’intérieur autant que le définit le rayon d’étalement :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-emulate-border-inset.png" alt="Émulation d'un deuxième bordure avec une box-shadow incrustée" /></p>
<p>On peut conjuguer plusieurs ombres <code>box-shadow</code> 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 <code>background-clip</code> vaut <code>padding-box</code>, 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 <em>padding</em>.</p>
<pre><code>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;
</code></pre>
<p>Vous pouvez utiliser ce Pen pour voir tout ça en action, avec un filtre <code>drop-shadow()</code> qui ajoute un effet de brillance.</p>
<iframe id="cp_embed_NGbqEX" src="//codepen.io/thebabydino/embed/NGbqEX?user=thebabydino&default-tab=result&slug-hash=NGbqEX&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<h2>Dessiner une cible aux bords lisses avec un seul élément (pas de pseudo-élément)</h2>
<p>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.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-target.svg" alt="La cible qu'on souhaite dessiner avec CSS" /></p>
<p>Une première approche consisterait à utiliser un gradient radial qu’on répète (<code>repeating-radial-gradient</code>) avec une structure comme celle-ci :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-target-structure.svg" alt="Illustration de la structure de la cible" /></p>
<p>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 ? <a href="http://www.w3.org/TR/css3-images/#radial-color-stops">D’après la spécification, ceci devrait fonctionner</a> :</p>
<pre><code>$unit: 1em;
background: repeating-radial-gradient(
#000 (-$unit), #000 $unit,
transparent $unit, transparent 3*$unit
);
</code></pre>
<p>Voici le Pen qui illustre cette idée :</p>
<iframe id="cp_embed_MKKxwQ" src="//codepen.io/thebabydino/embed/MKKxwQ?user=thebabydino&default-tab=result&slug-hash=MKKxwQ&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>Seulement voilà, IE a une opinion différente…</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/repeating-radial-negstop.png" alt="utiliser repeating-radial-gradient avec un premier point négatif : résultat attendu (à gauche) et IE (à droite)" /></p>
<p>Heureusement, <a href="https://twitter.com/dstorey/status/677269006508621824">ça a été corrigé avec Edge</a>, 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.</p>
<pre><code>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
);
</code></pre>
<p>Voici la démo :</p>
<iframe id="cp_embed_LGGawN" src="//codepen.io/thebabydino/embed/LGGawN?user=thebabydino&default-tab=result&slug-hash=LGGawN&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>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 !</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-repeating-radial-quad.png" alt="original (en haut à gauche), IE/Edge (en haut à droite), Firefox (en bas à gauche), Chrome (en bas à droite)" /></p>
<p>On pourrait utiliser cette astuce pour créer <a href="https://css-tricks.com/why-do-we-have-repeating-linear-gradient-anyway/">une transition moins abrupte</a> :</p>
<pre><code>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
);
</code></pre>
<p>Le résultat sur la démo :</p>
<iframe id="cp_embed_ZQQZOg" src="//codepen.io/thebabydino/embed/ZQQZOg?user=thebabydino&default-tab=result&slug-hash=ZQQZOg&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<p>Pour IE, c’est mieux (on avait déjà un résultat satisfaisant), pour Firefox également mais le problème persiste sous Chrome.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-radial-quad2.png" alt="original (en haut à gauche), IE/Edge (en haut à droite), Firefox (en bas à gauche), Chrome (en bas à droite)" /></p>
<p>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 <code>background-clip</code> et <code>box-shadow</code> ? On peut utiliser une <code>box-shadow</code> 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 <code>background-clip</code> sur <code>content-box</code> et on définit un padding suffisant pour qu’on ait une zone transparente entre le disque central et le cercle intérieur.</p>
<pre><code>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;
</code></pre>
<p>Voilà le résultat dans le Pen, aucun bord rugueux et aucun problème !</p>
<iframe id="cp_embed_09b434a2ae841dabeb83c429096ba01d" src="//codepen.io/css-tricks/embed/09b434a2ae841dabeb83c429096ba01d?user=css-tricks&default-tab=result&slug-hash=09b434a2ae841dabeb83c429096ba01d&theme-id=1&height=450" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="450"></iframe>
<h2>Des contrôles réalistes</h2>
<p>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.</p>
<p>Pour les pistes, on peut utiliser <code>-webkit-slider-runnable-track</code>, <code>-moz-range-track</code> et <code>-ms-track</code>. Pour les curseurs, on a <code>-webkit-slider-thumb</code>, <code>-moz-range-thumb</code> et <code>-ms-thumb</code>. Enfin, pour les barres de progression, il y a <code>-moz-range-progress</code>, <code>-ms-fill-lower</code> (les deux sont à gauche du curseur) et <code>-ms-fill-upper</code> (à 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.</p>
<p>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 :</p>
<pre><code>input[type='range']::-webkit-slider-thumb,
input[type='range']::-moz-range-thumb,
input[type='range']::-ms-thumb { /* les styles ici */ }
</code></pre>
<p>On doit toujours l’écrire de cette manière :</p>
<pre><code>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 */ }
</code></pre>
<p>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 <em>mixin</em> <code>thumb()</code> et je lui fournis des arguments pour gérer les incohérences :</p>
<pre><code>@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(); }}
</code></pre>
<p>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.</p>
<p>Voyons quelques exemples !</p>
<h3>Un bouton à l’aspect plastique</h3>
<p>Pour prendre un exemple visuel, dites-vous qu’on souhaite obtenir un curseur comme celui-ci</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/soft-plastic-thumb.png" alt="Un curseur plastique" /></p>
<p>À première vue, pour ce type de sélecteur, il devrait suffire d’ajouter un dégradé avec <code>background</code>, un autre avec <code>border-image</code>, il n’y a plus qu’à ajouter une <code>box-shadow</code> et c’est bon :</p>
<pre><code>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);
</code></pre>
<p>Effectivement, ça fonctionne (en utilisant un élément <code>button</code> à la place du curseur pour simplifier les choses) :</p>
<iframe id="cp_embed_eJZddq" src="//codepen.io/thebabydino/embed/eJZddq?user=thebabydino&default-tab=result&slug-hash=eJZddq&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<p>Mais notre sélecteur est rond et non carré, il manque donc seulement un <code>border-radius: 50%</code> et c’est bon ? Eh bien… ça ne fonctionne pas parce que nous utilisons <code>border-image</code> ce qui fait que <a href="http://stackoverflow.com/a/5707228/1397351"><code>border-radius</code> est ignoré sur l’élément lui-même</a>, même si, chose étrange, <a href="http://codepen.io/thebabydino/pen/PZNGEa">il reste appliqué sur <code>box-shadow</code></a> s’il y en a un.</p>
<p>Alors que devons-nous faire ? Utiliser <code>background-clip</code> ! Premièrement on donne un <em>padding</em> non nul à l’élément, pas de bordure et on le rend rond avec <code>border-radius: 50%</code>. Ensuite on superpose les arrière-plans dégradés, celui du dessus étant ajusté à la <code>content-box</code> (notez que le <em>clipping</em> est inclus dans la propriété raccourcie <code>background</code>). Enfin on ajoute deux <code>box-shadows</code>, 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.</p>
<pre><code>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);
</code></pre>
<p>Le résultat final est visible sur ce Pen :</p>
<iframe id="cp_embed_OMNRvw" src="//codepen.io/thebabydino/embed/OMNRvw?user=thebabydino&default-tab=result&slug-hash=OMNRvw&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<h3>Un curseur mat</h3>
<p>L’objectif à atteindre est de réaliser ce type de curseur:</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/matte-thumb.png" alt="Un curseur avec un bouton mat" /></p>
<p>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 <code>box-shadow</code> 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 <code>radial-gradient</code> sur les autres arrière-plans utilisés. La taille réelle de <code>background-size</code> pour le gradient est supérieure à la boîte de contenu et on peut donc la <a href="http://codepen.io/thebabydino/pen/zrqoJR">décaler vers le bas sans que le bord haut atteigne la limite du contenu</a>.</p>
<pre><code>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%;
</code></pre>
<p>Voici la démonstration du résultat obtenu</p>
<iframe id="cp_embed_rxeWwm" src="//codepen.io/thebabydino/embed/rxeWwm?user=thebabydino&default-tab=result&slug-hash=rxeWwm&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<h3>Un contrôle en 3D</h3>
<p>Prenons par exemple le bouton de ce curseur :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-3dthumb.png" alt="Un curseur avec un bouton en 3D" /></p>
<p>Celui-ci est plus complexe et il faut que les trois boîtes (contenu, <em>padding</em>, bordure) soient différentes afin qu’on puisse empiler différents arrière-plans et utiliser <code>background-clip</code> pour obtenir l’effet voulu.</p>
<p>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 <code>box-shadow</code> avec <code>inset</code> pour appuyer sur la limite entre la boîte de <em>padding</em> et la boîte de bordure.</p>
<pre><code>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;
</code></pre>
<p>Voici un démonstration du résultat qu’on obtient :</p>
<iframe id="cp_embed_LGNWex" src="//codepen.io/thebabydino/embed/LGNWex?user=thebabydino&default-tab=result&slug-hash=LGNWex&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<p>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 :</p>
<iframe id="cp_embed_LGNWdW" src="//codepen.io/thebabydino/embed/LGNWdW?user=thebabydino&default-tab=result&slug-hash=LGNWdW&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<p>On pourrait encore aller plus loin pour obtenir un meilleur rendu en ajoutant d’autres gradients ou en utilisant <code>background-blend-mode</code> 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 <code>border-radius: 50%</code>. Ensuite on lui applique un <em>padding</em>, 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.</p>
<pre><code>padding: .125em;
background:
radial-gradient(circle at 50% 10%,
#f7f8fa, #9a9b9f) content-box,
linear-gradient(#ddd, #bbb);
</code></pre>
<p>Voici le résultat qu’on obtient avec cette méthode :</p>
<iframe id="cp_embed_wMGJQR" src="//codepen.io/thebabydino/embed/wMGJQR?user=thebabydino&default-tab=result&slug-hash=wMGJQR&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<p>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 <code>::-webkit-slider-thumb</code> 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 à <code>::-webkit-slider-thumb</code>, Safari n’afficherait pas du tout la partie ronde.</p>
<h3>Une illusion de profondeur</h3>
<p>La piste utilisée pour ce curseur illustre bien l’idée voulue :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-slider-with-depth.png" alt="Un curseur sur une piste avec une impression de profondeur" /></p>
<p>Comme avant, pour l’élément, on donne une bordure transparente non nulle, un <em>padding</em> et des arrière-plans fixés sur différentes boîtes avec <code>background-clip</code> (celui avec <code>content-box</code> doit être au-dessus de celui avec <code>pading-box</code> qui doit être au-dessus de celui avec <code>border-box</code>). 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 <code>border-radius</code> au moins égal à la moitié de la zone de contenu plus deux fois le <em>padding</em> plus deux fois la bordure. On ajoute également une ombre <code>box-shadow</code> avec <code>inset</code> pour mettre en avant la limite du <em>padding</em>.</p>
<pre><code>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%;
</code></pre>
<p>Seulement voilà, on a un problème :</p>
<iframe id="cp_embed_dGMWBr" src="//codepen.io/thebabydino/embed/dGMWBr?user=thebabydino&default-tab=result&slug-hash=dGMWBr&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<p>En effet, vu le fonctionnement de <code>border-radius</code> (le rayon pour la zone de contenu est celui qu’on définit moins <code>border-width</code> moins le <em>padding</em>, 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.</p>
<p>Pour ce faire, on commence par s’assurer que la largeur de la zone de contenu est un multiple de sa hauteur (ici <code>15.75em = 9 x 1.75em</code>). 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 <code>background-size</code> est égal à hauteur de la zone de contenu, horizontalement et verticalement.</p>
<iframe id="cp_embed_YwqVgM" src="//codepen.io/thebabydino/embed/YwqVgM?user=thebabydino&default-tab=result&slug-hash=YwqVgM&theme-id=1&height=268" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="268"></iframe>
<h3>Des contrôles métalliques</h3>
<p>Comment obtenir un bouton comme celui-ci ?</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-metallic-control.png" alt="Un contrôle métallique" /></p>
<p>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 <code>border-radius: 50%</code>. Ensuite, on s’assure que <code>box-sizing</code> vaut <code>border-box</code> 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 <em>padding</em>. Voilà ce que ça donne :</p>
<pre><code>$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%;
}
</code></pre>
<p>Ça ne ressemble pas encore à grand-chose car l’effet est principalement accompli avec deux propriétés encore absentes : <code>box-shadow</code> et <code>background</code>.</p>
<iframe id="cp_embed_fa29ac06add80a7b5858c4e5b9f158cf" src="//codepen.io/thebabydino/embed/fa29ac06add80a7b5858c4e5b9f158cf?user=thebabydino&default-tab=result&slug-hash=fa29ac06add80a7b5858c4e5b9f158cf&theme-id=1&height=468" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="468"></iframe>
<p>Avant d’aller plus loin, décomposons l’ensemble :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-metallic-control-annotated.png" alt="Décomposition du controle métallique" /></p>
<p>En partant de l’extérieur vers l’intérieur, on a :</p>
<ul>
<li>Un anneau avec des diodes</li>
<li>Un anneau fin</li>
<li>Une zone perforée (qui contient le halo bleu autour de la zone intérieure</li>
<li>Une partie centrale ronde.</li>
</ul>
<p>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 <em>padding</em>. On crée l’anneau intérieur avec des ombres <code>box-shadow</code> en <code>inset</code>.</p>
<pre><code>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;
</code></pre>
<p>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 :</p>
<pre><code>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;
</code></pre>
<p>On n’y est pas encore tout à fait mais ça avance :</p>
<iframe id="cp_embed_984fe390d604f2c05c394d3bb5080e11" src="//codepen.io/thebabydino/embed/984fe390d604f2c05c394d3bb5080e11?user=thebabydino&default-tab=result&slug-hash=984fe390d604f2c05c394d3bb5080e11&theme-id=1&height=468" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="468"></iframe>
<p>On doit maintenant ajouter trois types d’arrière-plan en partant du haut vers le bas : ceux restreints à la boîte <code>content-box</code> (pour la zone centrale), ceux restreints à la boîte de <em>padding</em> (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).</p>
<p>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 <a href="http://www.sitepoint.com/the-cicada-principle-and-why-it-matters-to-web-designers/">un effet de texture avec cet empilement</a> 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 <a href="http://leaverou.github.io/conic-gradient/">une prothèse (<em>polyfill</em>)</a>.</p>
<pre><code>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;
</code></pre>
<p>Ça commence à ressembler à quelque chose !</p>
<iframe id="cp_embed_ae6d00286980b6fee6f69d7bae76f898" src="//codepen.io/thebabydino/embed/ae6d00286980b6fee6f69d7bae76f898?user=thebabydino&default-tab=result&slug-hash=ae6d00286980b6fee6f69d7bae76f898&theme-id=1&height=468" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="468"></iframe>
<p>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 <a href="http://lea.verou.me/css3patterns/#carbon-fibre">« fibre de carbone »</a> provenant de la galerie construite par Lea Verou il y a cinq ans.</p>
<pre><code>$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
</code></pre>
<p>On s’approche d’un résultat satisfaisant :</p>
<iframe id="cp_embed_4890d7ff30ef841899d26be2cf03a3f3" src="//codepen.io/thebabydino/embed/4890d7ff30ef841899d26be2cf03a3f3?user=thebabydino&default-tab=result&slug-hash=4890d7ff30ef841899d26be2cf03a3f3&theme-id=1&height=468" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="468"></iframe>
<p>Pour créer l’anneau extérieur, on utilise simplement un gradient conique :</p>
<pre><code>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;
</code></pre>
<p>Et voilà un contrôle métallique !</p>
<iframe id="cp_embed_4aee0470fba7176fc1c0c279cd4a40a9" src="//codepen.io/thebabydino/embed/4aee0470fba7176fc1c0c279cd4a40a9?user=thebabydino&default-tab=result&slug-hash=4aee0470fba7176fc1c0c279cd4a40a9&theme-id=1&height=468" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="468"></iframe>
<p>Il n’y a pas encore de diode, réglons ce problème !</p>
<p>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 <code>$k</code> pour indiquer le nombre de diodes allumées et on utilise un gradient bleu pour celles-ci et un gradient gris pour les autres.</p>
<p>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.</p>
<p>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).</p>
<pre><code>$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;
}
</code></pre>
<p>Voici le résultat final sur CodePen  :</p>
<iframe id="cp_embed_mVKayj" src="//codepen.io/thebabydino/embed/mVKayj?user=thebabydino&default-tab=result&slug-hash=mVKayj&theme-id=1&height=498" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="498"></iframe>
<h2>Afficher des ombres sur un plan perpendiculaire</h2>
<p>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 :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/AT-shadow-perpendicular-plane.png" alt="Des contrôles avec une ombre portant sur un plan horizontal en dessous" /></p>
<p>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 <code>background-clip</code> et <code>background-origin</code>. 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 <em>padding</em>, pour créer l’ombre on utilise un arrière-plan <code>radial-gradient()</code> avec <code>background-clip</code> et <code>background-origin</code> fixés à <code>border-box</code> pour créer l’ombre.</p>
<p>La mise en forme de base est obtenue de façon semblable au contrôle de la section précédente :</p>
<pre><code>$l: 6.25em;
$bw: .1*$l;
border: solid $bw transparent;
padding: 3px;
width: $l;
height: $l;
border-radius: 1.75*$bw;
</code></pre>
<p>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 <a href="https://www.youtube.com/watch?v=b9HGzJIcfDE">l’excellente conférence de Lea Verou sur <code>border-radius</code></a>).</p>
<iframe id="cp_embed_QyVNoM" src="//codepen.io/thebabydino/embed/QyVNoM?user=thebabydino&default-tab=result&slug-hash=QyVNoM&theme-id=1&height=170" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="170"></iframe>
<p>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 <code>content-box</code>. Juste en dessous, on a un gradient linéaire rattaché à la boîte de <em>padding</em>. 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).</p>
<pre><code>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
</code></pre>
<p>Voilà le bouton métallique (il n’y a pas encore l’ombre) :</p>
<iframe id="cp_embed_2ec400ed1518ad8e535c6052ff5cd833" src="//codepen.io/thebabydino/embed/2ec400ed1518ad8e535c6052ff5cd833?user=thebabydino&default-tab=result&slug-hash=2ec400ed1518ad8e535c6052ff5cd833&theme-id=1&height=250" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="250"></iframe>
<p>Pour l’ombre, on applique un troisième couche d’arrière-plan pour laquelle <code>background-clip</code> et <code>background-origin</code> sont liés à <code>border-box</code>. 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 <code>border-width</code>.</p>
<pre><code>radial-gradient(rgba(#787878, .9), rgba(#787878, 0) 70%)
50% bottom / 80% .75*$bw no-repeat border-box
</code></pre>
<p>Et voilà, vous pouvez utiliser ces boutons sur cette démo :</p>
<iframe id="cp_embed_VeBoXx" src="//codepen.io/thebabydino/embed/VeBoXx?user=thebabydino&default-tab=result&slug-hash=VeBoXx&theme-id=1&height=545" scrolling="no" allowtransparency="true" allowfullscreen="true" name="CodePen Embed" title="CodePen Embed" class="cp_embed_iframe " style="width: 100%; overflow: hidden;" frameborder="0" height="545"></iframe>
<p><code>background-clip</code> s’avère utile dans de nombreux cas ! Tout particulièrement lorsqu’on empile différents effets sur les bords des éléments.</p>
<p><em>Merci beaucoup à Ana Tudor qui a permis cette traduction !</em>
<style>
pre {
white-space: pre;
}
code {
white-space: pre;
}
</style></p>