Afficher des SVG via un composant React
Avec les avancées du développement Web, l’affichage d’icônes dans une application est passée d’une image de type GIF, JPEG ou PNG à l’utilisation de composants vectorielles ou communément appelés SVG (Scalable Vector Graphics). Le maintient d’icônes de type SVG dans une page web peut vite devenir assez fastidieuse (l’écriture XML étant assez verbeuse), surtout si vous disposez d’icônes redondantes utilisées un peu partout dans votre application.
Heureusement, il existe des solutions pour centraliser le code. Dans le cas d’une application développée sous React, il suffit de passer par la création d’un composant dédié qui s’appuierait sur un objet littéral contenant la définition de nos icônes.
Pour nous aider dans la conception de notre composant il faut revenir sur la structure d’un fichier SVG et voir quels sont les attributs à extraire pour définir notre fichier de configuration.
Définition du fichier de configuration
La forme d’un objet SVG est représentée par un ensemble de coordonnées permettant la génération d’une image vectorielle. Le code XML définissant un SVG dans une page web est généralement de la forme suivante :
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="300" height="200">
<title>Exemple simple de figure SVG</title>
<desc>
Cette figure est constituée d'un rectangle vert,
d'un segment de droite rouge, d'un cercle bleu et d'un texte.
</desc>
<rect width="100" height="80" x="0" y="70" fill="green" />
<line x1="5" y1="5" x2="250" y2="95" stroke="red" />
<circle cx="90" cy="80" r="50" fill="blue" />
<text x="180" y="60">Un texte</text>
</svg>
Ici nous sommes en présence de formes simples. Mais dans le cas de formes plus complexes, le SVG propose de définir des path à partir de commandes relatives. Les commandes relatives sont invoquées en utilisant des lettres minuscules. Plutôt que de déplacer le curseur vers des coordonnées absolues, elles le déplacent relativement à sa dernière position. Par exemple:
<path d="M10 10 h 80 v 80 h -80 Z" fill="transparent" stroke="black"/>
Le chemin va se positionner au point (10, 10), se déplacer horizontalement de 80 points vers la droite, puis de 80 points vers le bas, de 80 points vers la gauche, et enfin revenir à son point de départ. Bon d’accord c’est un carré mais vous voyez l’idée.
Il existe tout un ensemble de commandes permettant de définir des formes. Nous allons en aborder que quelques unes dans l’implémentation de notre composant React, libre à vous ensuite d’améliorer le code pour y ajouter de nouvelles formes. En attendant, basé sur ce que nous avons pu voir il est possible d’extrapoler la structure de notre fichier de configuration. Cependant il y a une dernière notion que nous n’avons pas abordé c’est la notion de grille. Votre image vectorielle a besoin d’un référentiel si vous souhaitez qu’elle soit scalable dans votre interface. C’est ici qu’intervient l’attribut viewBox
:
<svg width="16" height="16" viewBox="0 0 64 64">
L’image SVG suivante fait 16px par 16px. Toutefois, l’attributviewBox
définit que cet élément de 16 par 16 commence au point (0,0) et s’étend sur une grille de 64 unités sur 64 unités vers la droite et vers le bas de l’écran. 64 unités représentant 16 pixels, chaque unité vaut 4 pixels : cela permet de quadrupler la taille de l’image.
Maintenant que toutes les notions sont abordées définissons le besoin de notre fichier de configuration :
- un identifiant définissant le type d’icône
- le viewBox
- le path, une ligne, un cercle ou tout autre élément pouvant définir une forme
- les formes sont cumulables
ce qui nous donne :
export const Icons = {
ICON1: {
viewBox: '0 0 64 64',
path: ['M0 0 h 10 v 30 Z', 'M32 32 v 20 h -10 Z'],
rect: { x: 16, y: 24, width: 10, height: 10}
},
ICON2: {
viewBox: '0 0 512 512',
circle: { x: 256, y: 256, r: 100 }
},
...
}
Composant React
Reste plus qu’à définir le composant React qui s’appuis sur notre fichier de configuration. Nous allons prendre en compte les formes de types path et rect. Les autres formes peuvent être implémentées sur le même principe. Dans un premier temps nous devons établir les fonctionnalités de notre composant React régissant la génération d’un icône sur notre UI :
- On doit pouvoir lui associer un style personnalisable.
- On doit pouvoir lui associer un titre alternatif.
- On doit pouvoir lui passer un paramètre indiquant le type de l’icône à afficher depuis notre fichier de configuration.
- Notre icône doit éventuellement être cliquable et retourner une action.
On obtient donc une implémentation de ce type :
import React from 'react';
import PropTypes from 'prop-types';
export const Icon = props => {
const styles = {
svg: {
display: 'inline-block',
verticalAlign: 'middle'
},
path: {
fill: props.color
}
};
const getPath = () => {
if (props.icon.path == null) return;
if (Array.isArray(props.icon.path))
return props.icon.path.map((path, index) => {
return <path key={index} style={styles.path} d={path} />;
});
else return <path style={styles.path} d={props.icon.path} />;
};
const getRect = () => {
if (props.icon.rect == null) return;
if (Array.isArray(props.icon.rect))
return props.icon.rect.map((rect, index) => {
return (
<rect
key={index}
x={rect.x}
y={rect.y}
width={rect.width}
height={rect.height}
transform={rect.transform}
/>
);
});
else
return (
<rect
x={props.icon.rect.x}
y={props.icon.rect.y}
width={props.icon.rect.width}
height={props.icon.rect.height}
transform={props.icon.rect.transform}
/>
);
};
return (
<svg
style={styles.svg}
width={`${props.size}px`}
height={`${props.size}px`}
viewBox={props.icon.viewBox || props.viewBox}
onClick={props.onClick}
className={props.className}>
{props.title && <title>{props.title}</title>}
{getPath()}
{getRect()}
</svg>
);
}
Icon.propTypes = {
icon: PropTypes.object.isRequired,
size: PropTypes.number,
viewBox: PropTypes.string,
color: PropTypes.string,
onClick: PropTypes.func,
className: PropTypes.any,
title: PropTypes.string
};
Icon.defaultProps = {
size: 16,
viewBox: '0 0 1024 1024',
color: 'currentColor'
};
Les méthodes getPath()
et getRect()
retournent une chaîne en fonction du contenu proposé par le fichier de configuration, en vérifiant si le paramètre est de type Array
ou non. On remarque également que dans les propriétés par défaut on utilise la couleur de l’élément parent via l’attribut currentColor
.
Le composant peut ensuite être utilisé de la manière suivante dans vos fichiers JSX :
import { Icon, Icons } from './Icons/Index'; // En fonction de l'arborescence que vous aurez définit
const exampleView = props => {
return (
<Icon icon={Icons.ICON1}
className="class1 class2 classN"
title="My icon"}
/>
)
}
Dans le dossier qui contient vos deux fichiers JS définissant votre composant React, pensez à créer un fichier Index.js
qui les exporte :
export { Icon } from './Icon';
export { Icons } from './Icons';
Cela facilitera l’intégration dans vos fichiers JSX.
Ressources
- Site officiel React: https://reactjs.org/
- Introduction aux SVG: https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Introduction