11484 sujets

JavaScript, DOM et API Web HTML5

Bonjour,

[version courte :]

Je voudrais tester la lecture directe d'une icône .svg pour la mettre ensuite dans une variable js que je manipulerais par la suite. J'ai vu que l'on peut utiliser DOMparser et XMLHttpRequest mais je ne vois pas concrètement comment faire. Si quelqu'un peut m'aiguiller...

[version longue :]

Actuellement je fais appel à des icônes SVG pour mes interfaces que j'appelle via un script. Afin de garder mes scripts modulaires je ne code pas les icônes en dur dans le script, mais fait actuellement appel à la technique des sprites SVG, via un fichier externe pour bénéficier du cache (solution compatible à partir de Edge 13 et depuis bien plus longtemps pour les autres navigateurs populaires).
const injectSvgSprite = (targetElement, spriteId) => {
  const svgFile = 'sprites', // chemin du fichier SVG
        icon = '<svg><use xlink:href="/' + svgFile + '.svg#' + spriteId + '"></use></svg>'
  targetElement.insertAdjacentHTML('beforeEnd', icon)
}

Sauf que cette technique oblige à créer le fameux sprite (avec <symbol> et tout et tout) et même s'il existe des plugins node.js (mon environnement serveur) pour les créer je me pose tout de même la question d'une solution alternative avant de confirmer ce choix. En effet le vent tourne en faveur de l'injection directe des SVG en ligne et je voudrais si possible éviter de faire cohabiter deux systèmes d'appel aux SVG.
Modifié par Olivier C (17 Oct 2020 - 07:22)
Modérateur
Bonjour,

La question est un peu confuse. Du coup, la réponse est peut-être à côté.

1) Ce que tu appelles les sprites, ce n'est ni plus ni moins le fait d'inclure quelque part une ou des balises <svg> dans une page sans l'afficher (dans l'exemple codepen que tu donnes, c'est juste un "display:none" dans le css d'un svg unique qui fait ça), et de faire ensuite référence aux éléments internes de ce svg via un xlink:href pour construire chaque svg qui afficheront effectivement les icones.

2) Tu as donc 2 sortes de svg dans ta page, celui (ou ceux) qui contiennent le code qui dessinent les icones (on va appeler ça les svg dessinants), et ceux qui sont à l'endroit où doivent être affichées les icones (les svg affichants).

Si tu es bien organisé, tu n'as qu'un seul xlink:href à faire par icone dans les svg affichants (on peut toujours s'y ramener en encapsulant tout ce dont a besoin une icone dans une balise svg <g> qui serait dans le ou les fichiers svg dessinants). Donc bon, pas si compliqué. Ça tient en une ligne. Pas besoin de plugins pour ça !!!

Pour ce qui est de la mise en cache, il faut distinguer :
- la manière dont sont inclus les svg contenant le code dessinant. S'ils sont inclus dès la conception de la page, il n'y a pas de mise en cache particulière. S'ils sont lus a posteriori (genre avec de l'ajax), là, il peut y avoir mise en cache, mais ça va coûter en échange serveur.
- la manière dont sont inclus les svg affichants. Là, tu gagnes par le fait que le code dessinant est déjà dans ta page ailleurs. Ce n'est pas vraiment du cache. C'est juste que le navigateur pourra afficher les svg sans aucun échange avec le serveur.

Je ne vois pas ce qui pourrait être plus efficace que cette technique (svg dessinants + svg affichants).

3) Après, on peut aussi vouloir inclure des svg qui sont à la fois dessinants et affichants. C'est peut-être là que se trouve ta question. Comment faire ça ?

Dans ce cas, il me semble qu'il faudra un fichier svg par icone et inclure le contenu de ce fichier à chaque fois qu'on a besoin d'afficher une icone. Ça simplifie beaucoup la gestion ensuite.

On peut noter que si tu n'as que quelques icones par page, ça sera presque aussi efficace que la méthode précédente des svg dessinants et des svg affichants. Si par contre il y a beaucoup d'icones ça coutera forcément plus.

a) soit tu inclus les icones en dur (lors de la construction de la page côté serveur, tu lis le contenu du svg de l'icone comme tu le ferais pour n'importe quel fichier, et tu l'inclus tel quel dans la page à l'endroit où doit s'afficher l'icone).

Dans ce cas, la taille du fichier html envoyée du serveur au client risque de beaucoup grossir pour rien si tu as beaucoup d'icones et qui sont affichées plusieurs fois dans la page.

b) soit tu inclus tes icones a posteriori via par exemple de l'ajax, auquel cas, tu vas multiplier les échanges avec le serveur. En pratique, on fait une requête ajax qui récupère le svg (sous forme de text par exemple), et on l'inclut dans la page via par exemple un targetElement.insertAdjacentHTML('beforeEnd', icon);

Je n'aime pas trop cette méthode.

c) soit tu inclus tes icones en mettant simplement le nom du fichier svg comme valeur du src d'une balise <img>.

Cette méthode n'est pas à négliger. Dans ce cas, c'est le navigateur qui se débrouille pour optimiser au mieux le chargement de tout ça comme il le ferait pour n'importe quelle image.

4) Quid de DomParser() et XMLHttpRequest() ?
Je ne sais pas trop ce que tu entends par là, mais a priori, il s'agit de la méthode 3b) décrite plus haut (celle que je n'aime pas) : un coup d'Ajax pour récupérer par exemple un texte contenant le code svg d'une icone, puis un coup de insertAdjacentHTML() ou tout autre méthode du même genre pour inclure ce code tel quel dans le html de la page. Après, on doit peut-être pouvoir optimiser le résultat, mais pour des icones dont le code tient dans le cas général dans un seul élément path d'un seul élément symbol, je ne pense pas qu'il y ait grand chose à optimiser.

Evidemment, tout ça est éventuellement à adapter selon la manière dont tu t'y prends pour construire tes pages avec node.js, et d'une manière plus générale dépend de ce que font tes pages.

Amicalement,
Modifié par parsimonhi (17 Oct 2020 - 11:01)
Merci pour avoir pris la peine de me répondre de manière aussi détaillée. Ma technique ressemble à la description 1 et 2 :
<svg class="icon">
  <use xlink:href="#fish" />
</svg>

Mais je pensais plutôt utiliser la solution en dur au final, si j'ai bien compris via Ajax (la 4 ?), mais du coup je n'ai pas compris : pourquoi elle ne te plaisait pas ?
Modifié par Olivier C (17 Oct 2020 - 16:09)
Modérateur
Bonjour,
Olivier C a écrit :

Mais je pensais plutôt utiliser la solution en dur au final, si j'ai bien compris via Ajax (la 4 ?), mais du coup je n'ai pas compris : pourquoi elle ne te plaisait pas ?


Bah, charger des icones par ajax (c'est à dire faire des requêtes au serveur) lors du chargement initial d'une page, ça me parait hautement inefficace. Mieux vaut tout mettre dans la page côté serveur, sauf si bien sûr ces icones ne sont visibles que suite à certaines actions de l'utilisateur, auquel cas cela peut se justifier de les charger via ajax juste après ces actions utilisateurs.

Il y a aussi une question de volumétrie : y en a-t-il beaucoup ? et sont-elles chacune dans la page à plusieurs endroits ou bien en général présentes juste une fois ?

Amicalement,
parsimonhi a écrit :
Il y a aussi une question de volumétrie : y en a-t-il beaucoup ? et sont-elles chacune dans la page à plusieurs endroits ou bien en général présentes juste une fois ?

Oui, c'est ce que j'ai compris en lisant tous ces articles : il n'y a pas de solution universelle, "ça dépend"...
Modifié par Olivier C (18 Oct 2020 - 05:52)
Modérateur
Bonjour,

Sinon, concrètement, voici un exemple via un clic sur un bouton, de lecture d'un svg sur le serveur, la modification de sa couleur en rouge puis son affichage dans la page, et via un clic sur un autre bouton, modification de sa couleur en bleu.

Rien de très compliqué.

1) on crée un svg dans un fichier appelé hamburger.svg que l'on place sur le serveur (ici dans le même répertoire que la page, mais ça peut être ailleurs bien sûr).
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
    <rect x="0" y="0" width="16" height="2" rx="1"></rect>
    <rect x="0" y="7" width="16" height="2" rx="1"></rect>
    <rect x="0" y="14" width="16" height="2" rx="1"></rect>
</svg>


2) Page avec deux boutons qui déclenchent la lecture et l'affichage du svg, puis sa modification
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
svg
{
	display:block;
	margin:1em;
	width:5em;
	height:5em;
}
</style>
<script>
function getSvg(f)
{
	var name,xhr;
	// on retire l'extension ".svg"
	// name servira plus tard d'id au svg
	name=f.replace(/.svg$/,"");
	// si le svg est déjà dans la page, on ne fait rien
	if(document.getElementById(name)) return;
	// on est enfin prêt à récupérer le svg sur le serveur
	xhr=new XMLHttpRequest;
	xhr.onreadystatechange=function() {
		if (this.readyState==4 && this.status==200)
		{
			let s=this.responseText;
			// le svg est arrivé
			// on lui donne un id et on change sa couleur en rouge
			s=s.replace("<svg","<svg id=\""+name+"\" fill=\"red\"");
			// on l'affiche en bas de page
			document.body.insertAdjacentHTML('beforeEnd',s);
    	}
	};
	xhr.open('get',f); 
	xhr.send();
}
function modifySvg(id)
{
	// on change la couleur du svg en bleu
	document.getElementById(id).setAttributeNS(null,"fill","blue");
}
</script>
</head>
<body>
<h1>Lit un svg et le met dans une variable javascript</h1>
<p>Clique sur le bouton "Lecture"</p>
<p>Le svg "hamburger.svg" sera lu sur le serveur et affiché ensuite sous les boutons</p>
<p>Clique ensuite sur le bouton "Modifie"</p>
<p>Le svg sera modifié</p>
<button class="button" onclick="getSvg('hamburger.svg')">Lecture</button>
<button class="button" onclick="modifySvg('hamburger')">Modification</button>
</body>
</html>

Amicalement,
Modifié par parsimonhi (18 Oct 2020 - 12:05)
Meilleure solution