Les masques CSS

Comme c’est bientôt Noël, nous allons rêver un peu et regarder du coté des évolutions en matière de design web. On se souvient tous de notre première réaction quand on a découvert border-radius ou box-shadow ? Personnellement, ça a du être un truc du style « Oh, purée ! o_O » Et quand on a mixé ça avec les transitions et les animations, alors là on s’est transformé en demi-dieu du CSS. Et puis c’est retombé. Aujourd’hui, on va envoyer du lourd, on va sortir de son train-train habituel, et oublier ce fichu « support IE7 ». Laissez moi vous présenter les masques !

Guy Fawkes Masks

Le masquage est une technique de composition qui permet de combiner plusieurs images en une seule, où l’une des images va être utilisée pour masquer certaines parties des autres. Pour parler simplement, la première image correspond à l’élément graphique (la source) qui peut être composée d’images, de textes, de vidéos ou de n’importe quel autre contenu de notre page web. La seconde image est le masque et va agir comme un pochoir pour ne laisser visible qu’une certaine partie de notre source graphique.

Il existe deux catégories de masques :

  • Les masques basés sur une forme vectorielle définissant la zone visible de l’élément, c’est ce qu’on appelle le clipping.
  • Les masques basés sur une image en niveau de gris définissant quelles parties d’un élément sont masquées et quelles parties sont visibles, c’est le masking.

Si vous êtes habitués à utiliser les masques de Photoshop, vous pouvez facilement assimiler que le masking correspond aux masques de fusion alors que le clipping correspond aux masques vectoriels ou 1‑bit mask.

Masque de fusion et masque vectoriel

Au sein du W3C, la spécification des masques CSS vient de passer en stade CR (Candidate Recommandation), c’est à dire l’avant-dernier stade avant la Recommandation. Cette spécification tente de normaliser une syntaxe apparue dans le moteur WebKit en 2008. C’est pourquoi la majeure partie des propriétés sont déjà fonctionnelles dans les navigateurs basés sur ce moteur. Nous reviendrons sur le support un peu plus tard.

Clipping

Commençons par le clipping puisque, vous le savez peut-être, il existe depuis fort longtemps la propriété clip pour cela. Cette propriété CSS 2.1 permet, comme son nom l’indique, de rogner un élément. Malheureusement, son champ d’action est limité. Elle ne s’applique en effet qu’aux éléments en position: absolute, et seul un rectangle peut être utilisé comme forme de découpe. Néanmoins, des effets intéressants peuvent êtres produits, notamment lorsque l’on associe cette propriété aux transitions CSS, comme sur cette démo ci-dessous :

CSS clip()
Voir la démo en ligne sur CodePen

En CSS3, une nouvelle propriété de clipping voit le jour, c’est clip-path. Cette propriété (destinée à remplacer clip) permet de référencer une forme de découpe complexe. Il existe deux méthodes pour cela :

  • soit via des fonctions de formes
  • soit en référençant un <clipPath> SVG.

Le premier de ses avantages, face à clip, est qu’elle s’utilise sur tous les éléments, peu importe leur mode de positionnement. Le second avantage est bien entendu le contrôle plus poussé sur les formes de rognage. Découvrons-les ensemble.

Note : La propriété clip-path n’est en fait pas si nouvelle que ça. C’est au départ une propriété CSS spécifique à la mise en forme des documents SVG. La nouveauté est donc de pouvoir profiter de la puissance de SVG directement sur du contenu HTML.

clip-path : <basic-shape>

La première façon d’utiliser la propriété clip-path, c’est de se servir de fonctions de formes. Ces formes sont définies dans un autre module CSS, le CSS Shapes. Ce module définit cinq fonctions de formes aux noms qui parlent d’eux mêmes :

  • rectangle( x, y, width, height, border-radius)
  • inset-rectangle( top, left, bottom, right)
  • circle(x, y, rayon)
  • ellipse(x, y, rayonX, rayonY)
  • polygon( fill-rule, x1 y1, x2 y2, …, xn yn)

Par exemple, le CSS suivant crée un pentagone régulier (un polygone à cinq cotés de tailles identiques) comme forme de rognage :

.element {
	clip-path: polygon( 50% 0, 100% 38%, 80% 100%, 20% 100%, 0 38%);
}

Le résultat, accompagné d’autres types de formes, est donc le suivant :

Avatars avec clip-path()
Voir la démo en ligne sur CodePen (inspectez l’onglet CSS pour la réalisation des autres formes)

Le résultat est vraiment sympa et le code n’est pas trop incompréhensible. J’espère que vous commencez à entrevoir les nouvelles possibilités qui s’offrent à vous en matière de design web.

Mais il est possible d’aller encore plus loin, notamment grâce à la possibilité d’interpolation entre deux formes, basée sur les transitions et/ou animations CSS. Bien que la spécification définisse plusieurs types d’interpolation, aujourd’hui seules celles entre polygones sont possibles. De plus, le nombre de points doit être le même au départ et à la fin de l’animation, ce qui peut être contraignant (mais on peut espérer que cela évolue dans un futur proche).

Voici la même démo que pour la propriété clip, mais en utilisant clip-path: circle() et clip-path: polygon() combiné aux transitions CSS :

Masque SVG animé
Voir la démo en ligne sur CodePen

clip-path : <clipPath> SVG

La seconde méthode permet de référencer un élément SVG <clipPath> via la fonction url() de CSS. Cet élément peut se trouver directement au sein du document HTML ou être présent dans un fichier SVG externe.

Les possibilités offertes sont nettement plus étendues que les simples fonctions vues juste avant. Un élément <clipPath> peut en effet contenir autant d’éléments basiques SVG que l’on souhaite parmi :

  • Les formes basiques (<rect>, <circle>, <ellipse>, <polygon>)
  • Les chemins complexes (<path>)
  • Les textes (<text>)

Voici un exemple de code fonctionnel pour une forme simple (basé sur la démo précédente) référençant un <clipPath> au sein de la même page HTML :

.element {
	clip-path: url(#pentagon);
}

Et le SVG correspondant :

<svg>
	<defs>
		<clipPath id="pentagon" clipPathUnits="objectBoundingBox">
			<polygon points=".5,0 1,.38 .8,1 .2,1 0,.38" />
		</clipPath>
	</defs>
</svg>

Les points du polygone sont ici en unités relatives (entre 0 et 1), ce qui permet au code de pouvoir s’adapter à des éléments de tailles différentes. Cela est rendu possible grâce à l’utilisation de clipPathUnits="objectBoundingBox".

Il devient alors très facile de dessiner ses formes vectorielles dans un logiciel comme Illustrator puis de les exporter en SVG pour les utiliser et les animer depuis CSS. Voici un exemple de deux formes que vous reconnaîtrez facilement. Remarquez que chaque <clipPath> référence plusieurs chemins (<path>) ainsi que des éléments <animateTransform> pour les animations SVG. Dans ce cas précis, l’utilisation de la fonction polygon() serait totalement contre-productive et difficilement maintenable.

Animations avec clip-path()
Voir la démo en ligne sur CodePen

Support

En décembre 2013, les fonctions de formes CSS ne sont supportées qu’au sein de Chrome et Safari 6.1 avec le préfixe -webkit-. Le fait de référencer un élément SVG <clipPath> fonctionne dans Chrome, Safari 6.1 et Firefox sur le contenu HTML (notez quand même que Chrome et Safari sont très buggués sur ce point) et dans tous les autres navigateurs sur du contenu SVG.

Je reste assez évasif sur le support pour ne pas vous dresser un tableau trop sombre de la réalité. Ne gardez à l’esprit que le meilleur et les nombreuses possibilités qui s’offre à vous.

Masking

La deuxième partie de la spécification concerne le masking, c’est à dire la capacité à masquer une partie d’un élément en se basant sur une image. Pour ce faire, nous utilisons la propriété raccourcie mask qui, de la même façon que pour clip-path, s’emploie de deux façons :

  • soit en référençant une image via CSS
  • soit en référençant un <mask> SVG

Avant d’aller plus loin, retenez également qu’il existe deux façons de créer une image de masque. Pour faire le parallèle avec Photoshop encore une fois, pensez à la différence entre les masques de fusion et les masques d’écrêtages :

  • les masques de fusion sont basés sur une image en niveau de gris : les pixels noirs masquent tandis que les pixels blancs rendent visible.
  • les masques d’écrêtage quant à eux utilisent les pixels transparents d’un autre calque pour masquer le contenu

Même si en CSS/SVG ce n’est pas tout à fait la même chose, les notions sont très proches mais nous y reviendrons un peu plus tard.

mask : <image>

La première méthode pour un masque, c’est donc de référencer une image. Une image en CSS peut aujourd’hui être définie de deux manières :

  • une image GIF, JPG ou PNG référencée via la fonction url()
  • un dégradé CSS (linéaire ou radial)

Dans ce cas précis, le masque doit être de type alpha, autrement dit, un masque de transparence, équivalent aux masques d’écrêtage de Photoshop mentionnés plus haut.

Une fois le masque référencé, il existe toute une panoplie de propriétés CSS pour le contrôler et le modifier. En fait, ce sont les mêmes propriétés que celles de background, elles devraient donc vous être familières. Vous retrouverez mask-position, mask-repeat, mask-origin, mask-clip et enfin mask-size qui permet au masque de s’adapter plus facilement à la taille de la zone qui doit être masquée.

Voici un exemple utilisant une image PNG comme masque. Les zones noires de cette image laissent l’élément visible tandis que les zones transparentes le masque. J’ai également ajouté une transition CSS lors du survol de l’image sur la taille du masque avec mask-size, de mask-size: 100% 100% vers mask-size: calc(100% / 3) calc(100% / 3).

.element {
	mask: url(‘masque.png’);
	mask-size: 100% 100%;
	transition: mask-size .3s;
}

.element:hover {
	mask-size: calc(100% / 3) calc(100% / 3);
}
CSS mask
Voir la démo en ligne sur CodePen

mask : <mask> SVG

La seconde méthode pour un masque, c’est de référencer un <mask> SVG. Comme pour clip-path, cet élément peut se trouver directement au sein du document HTML ou être présent dans un fichier SVG externe. Les possibilités offertes sont également plus étendues puisqu’il est possible d’inclure plusieurs éléments complexes SVG (et même des images bitmaps) au sein du même masque, lesquels peuvent être remplis de couleurs, de dégradés, etc.

Dans ce cas là par contre, le masque est par défaut de type luminance. Ce n’est donc pas la couleur elle-même qui est importante mais bien la luminance de celle-ci. Pensez à une définition de couleur en mode HSL en CSS (Teinte, Saturation, Luminance). Le calcul de cette luminance est assez complexe mais pour faire simple on peut dire que :

  • un pixel blanc rend visible (luminance à 100%)
  • un pixel noir masque (luminance à 0%)
  • un pixel rouge (RVB 255,0,0) ou plutot HSL(0, 100%, 50%) masque à 50% (valeur de sa luminance)
  • attention car la valeur d’opacité entre également dans le calcul de cette luminance, et même se multiplie. Une couleur avec une luminance à 50% et une valeur d’opacité à 0.5 aura une luminance calculée de 25 % !

Pour réaliser le même effet que précédemment, il nous faut donc créer un élément <mask>, lequel contient une image.

<mask id=”mask” maskContentUnits="objectBoundingBox">
	<image xlink:href=”masque.png” width=”1” height=”1” preserveAspectRatio=”none” />
</mask>

Note : L’attribut maskContentUnits="objectBoundingBox" permet de définir les coordonnées relatives pour les éléments enfants du mask. Du coup, width et height sur l’image sont définis à 1. L’attribut preserveAspectRatio=”none” empeche de préserver l’aspect-ratio du masque de départ.

Puis de l’appeler depuis le CSS :

.element {
	mask: url(‘#mask’);
}

Malheureusement, le code dans cet état ne fonctionne pas. C’est tout simplement dû au fait que l’image de masque contient des pixels noirs et transparents. Nous venons de le voir, en SVG le masque est de type luminance, les pixels noirs masquent donc l’élément sur lequel le masque s’applique. Pour contourner ce problème, il est possible de créer une seconde image de masque, remplie de pixels blancs en lieu et place des pixels noirs. Cette technique, bien que viable, pose un soucis de maintenance puisque il nous faudrait gérer deux images différentes. Et c’est là où la force de SVG se libère ! Nous allons utilisez les filtres SVG pour convertir notre image noire en blanc, tout en conservant la transparence, et cela à la volée. C’est parti.

Ajoutons déjà le fameux filtre dans notre SVG :

<filter id="filter">
	<feFlood flood-color="white" />
	<feComposite in2="SourceAlpha" operator="in" />
</filter>

Ce filtre SVG se décompose en deux étapes :

  • la primitive <feFlood> remplie notre espace graphique de blanc
  • la primitive <feComposite> réalise l’étape de compositing en prenant comme première source le résultat de <feFlood> (par défaut) et comme seconde source (in2) notre sourceAlpha (notre image de masque donc). Les 2 « couches » sont assemblées avec l’opérateur in. (Testez l’opérateur out d’ailleurs pour comprendre plus facilement cette étape)

Ce filtre est ensuite appliqué à l’image au sein du <mask> :

<mask id=”mask” maskContentUnits="objectBoundingBox">
	<image xlink:href=”masque.png” width=”1” height=”1” preserveAspectRatio=”none” filter="url(#filter)" />
</mask>

Et voilà ! Pensez à inspecter le code de la démo précédente.

Pour terminer, je vous partage à nouveau deux effets effets que j’ai réalisé il y a quelques mois maintenant : l’effet de surbrillance et le flou progressif. Pour ce faire, j’ai simplement utilisé les techniques détaillées dans cet article.

CSS3 Create

Je vous laisse découvrir ces démos sur CodePen ou les articles détaillés sur CSS3Create.

Support

Le support du masking est plus ou moins équivalent au support du clipping :

  • Les propriétés de masques (mask et déclinaisons) sont très bien supportées dans les navigateurs basés sur WebKit (Chrome, Safari), et ce depuis 2008 (du à l’ancienne spécification comme expliqué plus haut) mais de manière préfixée avec -webkit-
  • Le fait de référencer un élément SVG <mask> fonctionne dans Firefox sur le contenu HTML et dans tous les autres navigateurs sur du contenu SVG.

Si le support cross-browser vous intéresse, je vous recommande la lecture de cet article : CSS Masks – How To Use Masking In CSS Now.

Poussez plus loin

Voici quelques informations en vrac pour éviter de vous arracher les cheveux dans vos tests.

Tout d’abord, il faut savoir que les différents compositing sont effectués dans un ordre précis par le navigateur. D’abord les filtres CSS, puis le clipping, le masking et enfin l’opacité. Cela signifie que les ombres appliquées avec box-shadow seront masquées par les masques CSS mais également celles appliquées avec le filtre drop-shadow(), ce qui peut vite devenir contraignant.

Ensuite, l’utilisation des propriétés clip-path, mask ou mask-image participent à la création d’un contexte d’empilement (stacking context) en CSS, à l’instar de la propriété opacity. Des résultats « surprenants » peuvent donc se produire. Je vous recommande chaudement la lecture de cette traduction de « Ce que personne ne vous a dit sur z‑index » si cette notion vous parait trop abstraite.

Enfin, la spécification prévoit deux propriétés CSS pour modifier le type du masque, soit à la création soit à l’utilisation. Il s’agit de mask-type et de mask-source-type. Les valeurs possibles sont alpha ou luminance, les deux types de masques possible.

Conclusion

Les masques permettent d’enrichir les interfaces avec de tout nouveaux types d’effets visuels, jusque là réservé au print. L’énorme avantage des masques sur le web, c’est bien entendu de pouvoir créer de nouvelles interactions, de nouvelles expériences utilisateurs, notamment en les combinant avec les transitions et animations, mais également en interagissant avec les périphériques comme la souris ou les écrans tactiles. Les masques sont également un élément primordial dans les mises pages avancées rendues possible avec les exclusions CSS, je vous invite également à vous y intéresser de près. Cet article n’est qu’une petite approche de ce que les masques ont à nous offrir.

Alors c’est à vous maintenant. Expérimentez, testez et créez les interfaces web de demain.

5 commentaires sur cet article

  1. STPo, le jeudi 12 décembre 2013 à 10:20

    Violence que tout ceci.

    On est encore dans la science-fiction sur pas mal de trucs, mais ça risque de faire bouger les lignes sur nos métiers front-end, où l’appellation « développeur front-end » rassemble encore indistinctement intégrateurs seniors qui bricolent en JS et développeurs parachutés du back-end qui ignorent tout ou presque de CSS.

    Cet article montre bien à quel point CSS devient une spécialité technique ardue qui interdit l’improvisation et la bidouille, et s’appuie de plus en plus sur des vraies notions graphiques (masques, modes de fusion, etc.)… « Designer CSS », ça y est on y est ?

  2. Vincent De Oliveira, le jeudi 12 décembre 2013 à 10:52

    @STPo : « Violence » ? Non, je ne pense pas. « Science-fiction » ? Non plus. Dans le cas précis des masques, seul IE est encore un peu à la traîne, quoique si tu passes par du 100% SVG, ça fonctionne très bien. Et pour un plus large support, l’article de « The Nitty Gritty » cité dans l’article te permet de le faire (certes, non sans mal)

    Je pense par contre que c’est utile de montrer que les attentes sont là, ça incite d’autres personnes à tester. Si la communauté montre de l’intérêt pour ces nouveautés, alors ce n’est que bénéfique pour le support.

    Tu as, en tout cas, parfaitement bien résumé la situation sur nos métiers.

    ;)

  3. CédricR, le jeudi 12 décembre 2013 à 11:03

    Bravo pour l’article, qui laisse entrevoir de belles choses à l’avenir.
    En lisant le titre, je me suis demandé si les masques avaient un rapport avec la valeur « text » propre à webkit pour la propriété background clip (http://css-tricks.com/snippets/css/gradient-text/). A priori il n’en est rien, même si cette dernière me paraissait également intéressante. Il est question de bien plus ici.
    Le métier d’intégrateur CSS va demander de plus en plus de spécialisation au vue de toutes ces alléchantes propositions.

  4. noodle, le jeudi 12 décembre 2013 à 11:50

    Juste en passant, le dernier lien de l’article envoi vers une 404.

    Merci pour les infos en tous cas je ne savais même pas que ça existait.

  5. STPo, le jeudi 12 décembre 2013 à 16:36

    Juste pour préciser : quand je dis « violence » et « science-fiction », j’entends par là que c’est loin d’être trivial comme techniques et que les outils pour faire tout ça de façon lissée sur la chaîne de prod sont encore à inventer. Et qu’accessoirement on se trimballe encore une palanquée de préfixes propriétaires voire de navigateurs totalement largués, ce qui interdit beaucoup de choses en prod à l’heure actuelle… mais ça va changer (espérons).