La Cascade

Un peu de tout sur CSS, HTML, SVG et JS,traduit du web anglophone
Rechercher

CSS Shapes, une introduction

par Sara Soueidan, 9 mai 2014, css, article original paru le 29 avril 2014 dans A List Apart

L'article de Sara Soueidan paru dans A List Apart a fait le buzz dernièrement. Sara présente ici très clairement ce qui sera l'avenir des formes et du design dans CSS.


Des rectangles dans des rectangles, telle est depuis toujours la structure de nos pages web. Depuis longtemps, nous tentons de nous libérer de leurs limitations en utilisant CSS pour créer des formes géométriques, mais ces formes n'ont jamais affecté le contenu situé à l'intérieur des éléments, ni la façon dont ces éléments affectent les autres éléments de la page.

La nouvelle spécification CSS Shapes change tout cela. Proposée par Adobe à la mi-2012, elle vise à fournir aux designers web une nouvelle façon de modifier le flux du contenu, à l'intérieur et autour de formes arbitrairement complexes - ce que nous n'avons jamais pu réaliser auparavant, pas même avec Javascript.

Dans l'image qui suit par exemple, remarquez comment le texte entoure les images circulaires. Sans Shapes, le texte serait rectangulaire, détruisant la touche sophistiquée qui élève son design à un niveau supérieur.

Notez comment le texte suit la forme du bol dans cet exemple. Avec CSS Shapes, nous pouvons créer le même genre d'effets pour le web.

Image d'une mise en page de magazine avec du texte qui s'enroule autour d'images circulaires.

Regardons maintenant la façon dont fonctionne Shapes, et comment vous pourrez l'utiliser.

Compatibilité navigateurs

Pour l'instant, CSS Shapes est supporté par Webkit Nightly et par Chrome Canary, mais le module niveau 1 en est au statut de Recommandation, donc les propriétés et la syntaxe définies dans les spécifications sont stables et devraient être mises en oeuvre d'ici peu par les autres navigateurs. (NdT: La compatibilité est excellente depuis longtemps) Ce niveau se concentre sur les propriétés qui modifient le flux du contenu autour d'une forme, et plus précisément sur la propriété shape-outside et celles qui lui sont associées.

Combinées avec d'autres fonctions de pointe telles que les masques (Clipping et Masking), les filtres CSS, et la composition d'images (Compositing et Blending), CSS Shapes va nous permettre de créer des design plus soignés et sophistiqués sans avoir à passer par des éditeurs graphiques comme Photoshop ou InDesign.

Les prochains niveaux de CSS Shapes concerneront l'insertion d'un contenu à l'intérieur d'une forme. Par exemple, aujourd'hui il est facile de créer un losange en CSS : il suffit d'appliquer à notre élément une rotation de 45° puis une rotation inverse au contenu situé à l'intérieur, de façon à ce qu'il retrouve son alignement horizontal sur la page. Cependant, le contenu ne sera pas affecté par la forme de son contenant et il restera rectangulaire. Lorsque la propriété shape-inside de CSS Shapes sera mise en oeuvre, le contenu pourra épouser la forme de son contenant, et nous aurons des design qui ressembleront à celui-ci.

Bientôt, CSS Shapes permettra au texte situé à l'intérieur de formes telles que ces losanges, de s'ajuster aux limites de son contenant, au lieu de déborder ou d'être tronqué.

Pour utiliser CSS Shapes dans Chrome Canary, il vous faudra activer le flag "fonctions expérimentales". Si vous n'êtes pas sûr de la marche à suivre, vous pouvez consulter cet article sur le blog d'Adobe.

Créer une forme avec CSS Shapes

Pour appliquer une forme à un élément, on utilise une des propriétés de Shapes et on lui passe une valeur qui est une fonction. C'est dans cette fonction que vous passerez les arguments définissant la forme que vous voulez donner à votre élément.

Successivement : la propriété, la valeur (qui est une fonction), les paramètres de la fonction

Les formes seront créées en utilisant l'une des fonctions suivantes :

  • circle()
  • ellipse()
  • inset()
  • polygon()

Chaque forme est définie par un ensemble de points. Certaines fonctions utilisent les points comme paramètres, d'autres utilisent des paramètres relatifs, mais toutes finissent par dessiner une forme comme un ensemble de points. Nous entrerons dans le détail des paramètres de chaque fonction dans les exemples qui suivent.

Une forme peut également être définie en l'extrayant d'une image, par un canal alpha. Lorsqu'on passe une image comme valeur d'une propriété Shapes, le navigateur extrait la forme de l'image à partir de shape-image-threshold (seuil), comme nous le verrons tout à l'heure. La forme est définie par les pixels dont la valeur est supérieure au seuil. L'image doit être compatible CORS (Cross-Origin Resource Sharing). Si l'image ne peut pas être affichée (par exemple si elle n'existe pas), aucune forme ne sera appliquée.

Les propriétés Shapes qui acceptent pour valeur ces fonctions sont :

  • shape-outside: Le contenu extérieur s'adapte à la forme
  • shape-inside : Le contenu intérieur s'adapte à la forme

Vous pouvez utiliser la propriété shape-outside avec la propriété shape-margin pour ajouter une marge autour de la forme afin de créer plus d'espace entre elle et le contenu. De la même façon, shape-inside fonctionne avec une propriété complémentaire shape-padding qui ajoute un padding à l'intérieur.

Déclarer une forme sur un élément peut se faire en une seule ligne de CSS :

.element {
  shape-outside: circle(

  ); /* le contenu s'enroulera autour du cercle défini sur l'élément */
}

ou bien :

.element {
  shape-outside: url(chemin/vers/image-avec-forme.png);
}

mais...

Pour que la forme soit appliquée à votre élément, il faut remplir deux conditions :

  1. L'élément doit être flotté. Il en sera peut-être différemment à l'avenir, mais pour l'instant, hors float point de salut.
  2. L'élément doit avoir des dimensions définies. Les hauteur et largeur de l'élément seront en effet utilisées pour établir un système de coordonnées.

Comme nous l'avons vu, les formes sont définies par un ensemble de points. Ces points ayant des coordonnées, le navigateur a besoin d'un système de coordonnées pour positionner chaque point sur l'élément. L'exemple qui précède fonctionnera donc si nous incluons quelque chose comme :

.element {
  float: left;
  height: 10em;
  width: 15em;
  shape-outside: circle();
}

Le fait de donner à un élément des dimensions spécifiques n'affecte pas son caractère responsif (nous y reviendrons).

Chaque forme étant définie par un ensemble de points positionnés en utilisant une paire de coordonnées, toute modification des coordonnées d'un point aura un effet sur la forme créée. Par exemple, l'image suivante montre une forme hexagonale créée à l'aide de la fonction polygon() . La forme consiste en six points. Modifier la coordonnée horizontale du point orange résultera en une forme différente, et affectera le flux de contenu à l'intérieur et/ou à l'extérieur de l'élément.

Si l'élément est flotté à droite et que cette forme lui est appliquée, le flux du contenu situé sur sa gauche sera modifié si les coordonnées du point orange sont changées dans la fonction polygon().

Si l'élément est flotté à droite et que cette forme lui est appliquée, le flux du contenu situé sur sa gauche sera modifié si les coordonnées du point orange sont changées dans la fonction polygon().

La boîte de référence

Les formes CSS sont définies et créées à l'intérieur d'une boîte de référence, qui est la boîte utilisée pour dessiner la forme. En plus des hauteur et largeur de l'élément, les boîtes (nommées d'après le modèle de la boîte) - margin box, content box, padding box, et border box - sont utilisées pour spécifier l'étendue de la forme sur l'élément.

Par défaut, la référence est margin box, donc si l'élément auquel vous appliquez une forme a une marge en bas, la forme que vous définissez ne se limitera pas aux dimensions de l'élément lui-même, elle s'étendra jusqu'à la marge en question. Si vous voulez utiliser une des autres valeurs de boîtes, vous pouvez l'indiquer en même temps que la fonction passée dans les propriétés :

shape-outside: circle(250px at 50% 50%) padding-box;

Le mot-clé padding-box dans la règle ci-dessus indique que la forme sera appliquée et restreinte à la padding box de l'élément. La fonction circle() définit une forme circulaire, sa taille et sa position sur l'élément.

Définir des formes avec les fonctions

Commençons avec un avatar circulaire entouré de texte, les profils d'utilisateurs ou les témoignages de consommateurs sont souvent présentés ainsi.

Avec CSS Shapes, le texte s'enroule autour de la forme au lieu de rester rectangulaire.

Nous allons utiliser la fonction circle() pour appliquer une forme circulaire à l'image de profil en utilisant le balisage suivant :

<img
  src="http://api.randomuser.me/0.3.2/portraits/men/7.jpg"
  alt="profile image"
/>
<p>
  Lorem ipsum dolor sit amet, consectetur adipisicing elit. Harum
  itaque nam blanditiis eveniet enim eligendi quae adipisci?
</p>
<p>
  Assumenda blanditiis voluptas tempore porro quibusdam beatae
  deleniti quod asperiores sapiente dolorem error! Quo nam quasi
  soluta reprehenderit laudantium optio ipsam ducimus consequatur enim
  fuga quibusdam mollitia nesciunt modi.
</p>
Peut-être vous demandez-vous pourquoi nous n'utilisons pas
border-radius pour arrondir l'image ? La réponse est que la propriété
border-radius n'a aucun effet sur le flux ou la superficie du contenu
interne ou externe, il n'agit que sur la bordure d'un élément et sur
le background. Ce dernier s'adapte en fonction des modifications de
bordure, et c'est tout. Le contenu interne demeure rectangulaire et le
texte à l'extérieur voit et traite l'élément comme s'il était
rectangulaire - parce qu'en fait il l'est toujours.

Pourquoi ne pas utiliser la propriété border-radius pour rendre l'image circulaire - comme on le fait habituellement pour arrondir une image ou un élément :

img {
  float: left;
  width: 150px;
  height: 150px;
  border-radius: 50%;
  margin-right: 15px;
}
Sans CSS Shapes, le texte voit l'image comme un rectangle, il entoure une forme rectangulaire et non circulaire.

Dans un navigateur non compatible avec CSS Shapes, le contenu extérieur se positionnera autour de l'image comme si elle n'était pas circulaire. Avec les navigateurs anciens, ce sera donc la solution de remplacement (fallback).

Pour adapter le flux de contenu à une certaine forme, vous pouvez utiliser les propriétés Shapes :

img {
  float: left;
  width: 150px;
  height: 150px;
  border-radius: 50%;
  margin-right: 15px;

  shape-outside: circle();
  shape-margin: 15px;
}

De cette façon, le texte "verra" la forme circulaire appliquée à l'image et se lovera autour d'elle, comme dans la première illustration. En cas d'incompatibilité navigateur, c'est la deuxième illustration qui s'affichera.

On peut utiliser la fonction circle() telle quelle, ou lui passer des paramètres. La syntaxe officielle est :

circle() = circle([<shape-radius]? [at <position>]?)

Le point d'interrogation indique que ces paramètres sont optionnels et peuvent être omis. En cas d'omission d'un paramètre, celui-ci prend une valeur par défaut. Lorsque vous utilisez circle() tel quel, sans définir explicitement sa position, il positionnera par défaut le cercle au centre de l'élément.

On peut spécifier le rayon du cercle avec n'importe quelle unité de longueur (px, em, pt, ...). On peut également spécifier le rayon en utilisant closest-side (côté le plus proche) ou furthest-side(côté le plus éloigné), maisclosest-side` est la valeur par défaut, ce qui signifie que le navigateur définira le rayon comme étant la longueur entre le centre de l'élément et son côté le plus proche.

shape-outside: circle(
  farthest-side at 25% 25%
); /* définit un cercle dont le rayon est égal à la moitié du côté le plus long, et qui est positionné aux points de coordonnées 25% 25% dans le système de coordonnées de l'élément */

shape-inside: circle(
  250px at 500px 300px
); /* définit un cercle dont le centre est positionné à 500px horizontalement et 300px verticalement, avec un rayon de 250px */

La fonction ellipse() est similaire à la fonction circle() , elle utilise les mêmes listes de valeurs, mais au lieu d'avoir un seul paramètre de rayon, elle en a deux : un pour la longueur du rayon sur l'axe des x, l'autre pour l'axe des y.

ellipse() = ellipse([<shape-radius>{2}]? [at <position>]?)

La fonction inset() est utilisée pour créer des formes rectangulaires. Dans la mesure où les éléments sont déjà rectangulaires, nous pouvons l'utiliser d'une manière plus fine, par exemple en créant des rectangles à bords arrondis, avec un flux de contenu qui s'adapte.

La fonction inset() prend de 1 à 4 valeurs offset qui définissent les retraits (offset) depuis les bords des boîtes de référence vers l'intérieur. Ils spécifient l'endroit où le rectangle sera inséré à l'intérieur de l'élément. Les coins arrondis sont définis exactement de la même façon que les border-radius en utilisant de 1 à 4 valeurs, en conjonction avec le mot-clé round .

inset() = inset(offset{1,4} [round <border-radius>]?)

Voici comment on créera un rectangle arrondi sur un élément flotté :

.element {
  float: left;
  width: 250px;
  height: 150px;
  shape-outside: inset(0px round 100px) border-box;
}

La dernière fonction Shapes est le polygon() , qui permet de définir des formes arbitraires plus complexes en utilisant une quantité de points. La fonction prend un ensemble de paires de coordonnées, chaque paire spécifiant la position d'un point, et l'ensemble des points définissant la forme.

Dans l'exemple qui suit, une image flottée à droite prend toute la hauteur de l'écran en utilisant les unités viewport. Nous voulons que le texte de gauche suive les contours du sablier situé à l'intérieur de l'image, et nous utilisons la fonction polygon() pour définir une forme irrégulière.

Pour l'image ci-dessus, notre CSS sera :

img.right {
  float: right;
  height: 100vh;
  width: calc(100vh + 100vh / 4);
  shape-outside: polygon(
    40% 0,
    100% 0,
    100% 100%,
    40% 100%,
    45% 60%,
    45% 40%
  );
}

Pour les coordonnées des points définissant l'image, vous pouvez utiliser des unités de longueur ou des pourcentages, comme je l'ai fait ici.

Ces quelques lignes suffisent à produire l'image ci-dessus, cependant vous avez pu constater que la fonction n'a pas eu d'effet sur le reste de l'image à l'extérieur de la forme définie. En fait, appliquer une fonction Shapes à un élément - que ce soit une image, un container, ou quelque chose entre les deux - n'affectera rien d'autre que l'aire du flux de contenu. Les backgrounds, les bordures et tout le reste demeurent inchangés.

Si nous voulons visualiser la forme du polygone que nous avons créé, nous devons découper les parties de l'image qui se trouvent à l'extérieur de la forme. Pour cela, nous pouvons utiliser la propriété clip-path du module CSS Masking.

La propriété clip-path prend les mêmes fonctions et valeurs que la propriété Shapes. Si nous passons la définition du polygone utilisée dans la propriété shape-outside à la propriété clip-path , nous découpons la partie de l'image correspondante.

img.right { float: right; height: 100vh; width: calc(100vh + 100vh/4); shape-outside: polygon(40% 0, 100% 0, 100% 100%, 40% 100%, 45% 60%, 45% 40%); /_ clip the image to the defined shape _/ clip-path: polygon(40% 0, 100% 0, 100% 100%, 40% 100%, 45% 60%, 45% 40%); } Le résultat est le suivant :

La propriété clip-path nécessite encore d'utiliser des préfixes, elle marchera dans Chrome avec le préfixe -webkit- . Vous pouvez voir une démo live ici. (NdT: la compatibilité de clip-path est excellente aujourd'hui).

La propriété clip-path accompagne à merveille les propriétés Shapes, car elle permet de visualiser les formes en découpant toute partie de l'élément qui se trouve en dehors de la forme définie. Vous l'utiliserez sans doute souvent avec les propriétés Shapes.

La propriété polygon() prend par ailleurs un mot-clé optionnel, qui peut être soit nonzero soit evenodd . Ce mot indique la façon de traiter les surfaces à l'intérieur de la forme polygonale qui pourraient être en intersection. Pour les détails, vous pouvez consulter la propriété SVG fill-rule.

Définir une forme en utilisant une image

Pour définir une forme en utilisant une image, cette dernière doit avoir un canal alpha à partir duquel le navigateur peut extraire la forme.

Une forme est définie par les pixels dont la valeur alpha est supérieure à un certain seuil. Celui-ci est par défaut égal à 0.0 (entièrement transparent), mais vous pouvez le définir explicitement en utilisant la propriété shape-image-threshold . Dans ce cas, tout pixel qui n'est pas transparent sera utilisé comme partie de la forme définie à partir de l'image.

Dans un niveau futur de CSS Shapes, il est possible que l'on utilise la luminance d'une image plutôt que les données alpha, auquel cas shape-image-threshold sera étendu pour appliquer un seuil alpha ou luminance.

Nous allons utiliser l'image suivante pour définir une forme sur un élément et envelopper celui-ci de texte.

image d'une feuille noire sur fond blanc

Avec shape-outside et url() pointant vers notre image, nous pouvons définir le flux du contenu autour de notre élément en forme de feuille.

.leaf-shaped-element {
  float: left;
  width: 400px;
  height: 400px;
  shape-outside: url(leaf.png);
  shape-margin: 15px;
  shape-image-threshold: 0.5;
  background: #009966 url(path/to/background-image.jpg);
  mask-image: url(leaf.png);
}

Bien sûr, si vous deviez appliquer un background à l'élément, ce background devrait être découpé en dehors de la forme définie, ce que nous permet la propriété mask-image (accompagnée des préfixes appropriés) du Module Masking, puisque la propriété clip-path ne prend pas d'image alpha comme valeur. Voici le résultat :

Si vous créez des formes complexes, il pourra être préférable de définir une forme en utilisant une image (par exemple en utilisant le canal alpha d'une image sur Photoshop) plutôt que de définir des points.

Il est également préférable d'utiliser une image plutôt qu'une fonction lorsque vous avez plusieurs float ou plusieurs surfaces réservées à l'intérieur d'un élément - parce qu'on ne peut pas, pour l'instant du moins, déclarer plusieurs formes sur un élément. Par contre, si votre image est constituée de plusieurs zones, le navigateur pourra les extraire et les utiliser.

CSS Shapes et design responsif

CSS Shapes est-il compatible avec mon workflow responsif ? La spécification en cours (shape-outside) a déjà réglé la question, elle vous permet de spécifier les dimensions de l'élément soit en pourcentages, soit en unités de longueur, et les points composant la forme (paramètres de la fonction forme) peuvent également être définis en pourcentages.

shape-inside n'est pas aussi avancé, mais c'est parce qu'il a été repoussé au Module Niveau 2. Ses limitations actuelles seront résolues au prochain niveau.

Les outils pour Shape

La création de formes complexes à travers les fonctions Shapes peut faire peur, en particulier s'agissant de polygon() . Heureusement, l'équipe d'Adobe a créé des outils interactifs qui nous facilitent la vie. Bear Travis a créé une collection d'outils Shapes qui nous permettent de créer des formes polygonales de manière visuelle. L'outil génère ensuite la fonction pour nous. C'est très utile avec une limite : vous ne pourrez pas insérer une image pour créer votre forme. Un outil plus avancé et plus interactif a été développé par Adobe. Il a été publié récemment en tant qu'extension de l'éditeur de texte d'Adobe, Brackets. Il vous permet de visualiser et éditer des formes directement dans votre navigateur, et il comporte une fonction live preview qui met à jour la page lorsque vous modifiez les valeurs Shapes de votre CSS. Vous pouvez ainsi voir instantanément la façon dont vos formes interagissent avec les autres éléments de la page.

Création et modification d'une forme polygonale avec le mode live preview de Bracket. Capture d'écran par Razvan Caliman.

Razvan Caliman a écrit un article sur le blog de Bracket qui explique comment vous pouvez intégrer l'éditeur Shapes dans Bracket et l'utiliser dès aujourd'hui. (NdT: Il semble que le lien ne fonctionne plus)

L'avenir : Exclusions CSS

La spécification CSS Shapes couvrait autrefois Shapes et Exclusions, mais les deux ont été séparées. Shapes définit les propriétés shape-inside et shape-outside , Exclusion de son côté définira les propriétés nous permettant d'entourer de contenu les éléments non flottés, tels que les éléments positionnés absolument. On pourra de la sorte entourer de contenu la forme tout entière, de tous côtés, comme on le voit dans l'image qui suit.

À l'avenir, CSS Exclusions nous permettra d'envelopper de texte une forme, telle que cette citation au milieu. Si la forme était circulaire, le texte en suivrait le contour. Image par Justin Thomas Kay.

Des mises en page similaires, avec des éléments positionnés absolument au centre d'un article et un flux de contenu environnant, seront possibles.

CSS Shapes, un peu plus loin

La spécification en cours est un premier pas. Bientôt, de nouvelles options nous donneront plus de contrôle sur la création de formes et le flux de contenu, et la création de designs sera une question de quelques lignes de code. Pour l'instant, les éditeurs du spec sont concentrés sur la publication de shape-outside , et il est vraisemblable que CSS Shapes sera utilisable avec les meilleurs navigateurs avant la fin de cette année.

Vous pouvez d'ores et déjà utiliser CSS Shapes dans le cadre d'un workflow conçu en amélioration progressive, puisque les fallbacks sont faciles à mettre en oeuvre. Je les utilise depuis peu dans mon propre site web, et le fallback est très "normal". Pour des designs plus complexes, vous pouvez utiliser un script pour vérifier la compatibilité navigateur et fournir une solution de rechange si la réponse est négative. Vous pouvez étendre les tests de Modernizr avec ce script pour tester la compatibilité avec shape-outside.

CSS Shapes construit un nouveau pont entre les design web et imprimé. Les exemples fournis dans cet article sont simples, mais devraient vous donner une bonne base pour créer des designs aussi complexes que ceux d'un magazine ou d'un poster, sans avoir à vous soucier de savoir si votre maquette pourra être recréée à l'écran. Quel que soit votre domaine d'exploration, depuis les mises en pages non rectangulaires jusqu'à l'animation de formes, le moment est venu d'expérimenter !

NdT: Voir aussi le formidable article de Sara Soueidan Animating CSS Shapes with CSS Animations and Transitions pour l'animation des CSS Shapes (en anglais).

Compatibilité à ce jour

Data on support for the css-shapes feature across the major browsers from caniuse.com

(Un grand merci à Ire Aderinokun !)

Autres ressources externes

Articles de Sara Soueidan traduits dans La Cascade

Voir la page de Sara Soueidan et la liste de ses articles dans La Cascade.
Article original paru le 29 avril 2014 dans A List Apart
Traduit avec l'aimable autorisation de A List Apart et de Sara Soueidan.
Copyright A List Apart © 2014