SVG avec JavaScript

De EduTech Wiki
Aller à la navigation Aller à la recherche
Initiation à la pensée computationnelle avec JavaScript
Module: JavaScript dans le navigateur ◀▬ ▬▶
◀▬▬▶
à finaliser débutant
2020/11/23
Prérequis
Catégorie: JavaScript

Introduction

SVG et JavaScript partagent une évolution assez similaire, car les deux technologies ont connu des hauts et des bas depuis leurs créations, mais depuis quelques années elles ont été (ré)découvertes. La combinaison entre les deux représente donc un moyen pour améliorer les applications web interactives notamment en ce qui concerne :

  • Le design d'interfaces utilisateurs "graphiques" (e.g. dessin d'animaux, cartes géographiques, ...) ;
  • La création d'animations "stand-alone" ou qui s'intègrent avec le contenu d'une page web ;
  • La création de représentations graphiques dynamiques (e.g. visualisation de données) ;
  • L'adaptation des éléments graphiques à différentes tailles et résolutions des écrans grâce à la nature vectorielle des SVG.

Cet article propose des éléments conceptuels et techniques qui aident l'intégration des deux technologies dans une version plus récente par rapport au Tutoriel SVG dynamique avec DOM.

Prérequis

Pour lire cet article, les connaissances suivantes sont nécessaires :

Il faut également une connaissance de bases des deux technologies, voir à ce propos surtout les pages :

Ces pages proposent d'autres liens qui peuvent être utiles à la compréhension du contenu de l'article.

Exemples traités dans l'article

Les exemples proposés et analysés dans cet article sont disponibles dans un repository GitHub. Ils suivent une numérotation qui sera utilisée pour les référencer également dans le texte, par exemple: 00-01.

Pour des raisons d'espace, seulement les parties du code indispensables à la compréhension du concept expliqué seront affichées. La référence aux lignes intéressées est fournie pour pouvoir se repérer dans le code source.

Les pages des exemples utilisent la version 4 du framework CSS Bootstrap pour améliorer la mise en page. Cependant, ce framework n'est pas nécessaire pour le fonctionnement des exemples, ou plus en général pour l'intégration de SVG et JavaScript.

Comprendre l'intégration entre SVG et JavaScript

Le Document Object Model (DOM) d'une page web avec un élément SVG.

L'intégration entre SVG et JavaScript se fait tout simplement à travers le Document Object Model (DOM) de la page. Pour rappel, le DOM représente l'arborescence hiérarchique des éléments/balises qui composent le code source d'une page HTML.

En raison du fait que SVG utilise une structure de type XML, basée donc sur des éléments/balises emboîtes, un SVG dans un document HTML5 n'est rien d'autre qu'un noeud/element dans le DOM. L'élément SVG "principale" représente le noeud de contact avec le DOM de la page, et tout les éléments qui se trouvent à l'intérieur du code SVG sont des sous-noeuds ou sous-éléments.

De ce fait, on peut accéder à ces éléments selon le même principe qu'on utilise pour accéder à des éléments HTML avec JavaScript. Une fois que la page a été téléchargée par le navigateur et le parsing du DOM effectué, dans le document de la page on retrouve également les éléments SVG.

Il faut cependant faire attention à la modalité d'intégration du SVG dans la page : on ne peut pas intégrer SVG et JavaScript si le SVG est incorporé en tant que image (i.e. à travers la balise <img>). Pour être plus précis, si on incorpore un SVG en tant que "simple" image, c'est comme si on perdait la différence entre une image vectorielle et une image matricielle. En effet, le SVG serait traité comme un bloc unique, sans la possibilité de discriminer parmi les différences éléments/balises qui le composent.

L'intégration entre SVG et JavaScript se fait à travers le DOM de la page. Pour ce faire, il ne faut pas incorporer le SVG en tant que image.

Principes techniques d'intégration entre SVG et JavaScript

Une fois constaté que l'intégration entre SVG et JavaScript se fait plus ou moins de la même manière qu'entre les éléments HTML et JavaScript, nous allons voir dans les détails comment cette intégration se fait au niveau du code source. Nous allons utiliser quelques exemples de base dans lesquels nous verrons :

  1. Comment JavaScript identifie les éléments dans un SVG incorporé dans la page
  2. Comment sélectionner des caractéristiques/attributs spécifiques des éléments (e.g. la couleur)
  3. Comment manipuler les caractéristiques/attributs d'un élément SVG avec JavaScript (e.g. changer sa couleur)
  4. Comment manipuler un élément SVG à travers un événement déclenché par un élément qui se trouve sur la page, et donc en dehors de l'élément SVG lui-même (e.g. cliquer sur un bouton)
  5. Le processus inverse du point précédent, c'est-à-dire qu'un événement déclenché par un élément dans le SVG peut manipuler un élément HTML qui se trouve dans la page (e.g. modifier le contenu d'un noeud)

Identifier un élément SVG

Dans l'exemple 00-01, l'élément SVG est incorporé tout simplement à travers la balise <svg>...</svg>. À l'intérieur de l'élément svg nous avons placé trois circle avec des tailles et des couleurs différentes. Voici le code limité à la partie qui nous intéresse (Lignes 38-44) :

<div>
    <svg viewBox="0 0 100 100" id="mySVG">
        <circle cx="50" cy="50" r="40" fill="blue" />
        <circle cx="50" cy="50" r="25" fill="red" />
        <circle cx="50" cy="50" r="10" fill="yellow" />
    </svg>
</div>

Il s'agit d'un simple svg avec deux particularités à noter :

  1. Nous n'avons pas déclaré une taille pour l'élément mais exclusivement son viewBox.
    De cette manière le svg va s'adapter directement à la taille de l'élément qui le contient (dans ce cas le div). Si par la suite on veut définir la taille de manière plus spécifique, on pourra le faire à travers du CSS ou avec des attributs de l'élément svg lui-même.
  2. Nous avons attribué à l'élément svg un attribut id="mySVG"
    Comme dans le cas des éléments HTML interactifs, il faut que JavaScript puisse par la suite savoir identifier précisément à quel élément appliquer les instructions. L'identification peut se faire de différentes manières, mais pour cet exemple nous avons choisi la plus simple, avec l'attribut id.

Grâce à l'attribut id, nous pouvons donc créer un script qui identifie l'élément svg afin qu'on puisse par la suite le manipuler avec JavaScript. C'est ce qui est fait à la ligne 69 :

//Identify the element and print it to the console
var theSVG = document.querySelector("#mySVG");
console.log("This is the result of the document.querySelector(\"#mySVG\") instruction.");
console.log(theSVG);
//Identify the circles
var theCircles = document.querySelectorAll("#mySVG circle");
console.log("This is the result of the document.querySelectorAll(\"#mySVG circle\") instruction.");
console.log(theCircles);

À la ligne 73, par contre, nous utilisons le même principe mais cette fois-ci pour identifier tous les élément circle qui se trouvent à l'intérieur du svg. Si vous testez la

et vous ouvrez la console de votre navigateur, les deux identifications vont être confirmées dans le output :

This is the result of the document.querySelector("#mySVG") instruction.
<svg viewBox="0 0 100 100" id="mySVG">...</svg>
This is the result of the document.querySelectorAll("#mySVG circle") instruction.
NodeList(3) [circle, circle, circle]

En effet, la deuxième instruction qui identifie les cercles obtient comme résultat une liste de noeuds, dans ce cas 3 en correspondance des trois éléments circle contenus dans le svg.

Tout comme pour les éléments HTML, nous avons simplement créé deux références symboliques à travers deux variables dans le code JavaScript :

  1. theSVG se réfère à l'élément svg "principale"
  2. theCircles se réfère aux 3 éléments circle à son intérieur.

Accéder à des caractéristiques/attributs de l'élément SVG

Nous ne sommes pas obligés de passer par l'élément svg "principal" pour identifier des éléments qui se trouvent à son intérieur. Dans l'exemple 00-02 nous allons plutôt associer un attribut id différent aux trois cercles :

<div>
    <svg viewBox="0 0 100 100" id="mySVG">
        <circle cx="50" cy="50" r="40" fill="blue" id="outerCircle" />
        <circle cx="50" cy="50" r="25" fill="red" id="mediumCircle" />
        <circle cx="50" cy="50" r="10" fill="yellow" id="innerCircle" />
    </svg>
</div>

Grâce à ce passage, nous pouvons maintenant utiliser les 3 id différentes pour créer des références symboliques aux cercles en JavaScript. Au lieu de créer 3 variables différentes, nous utilisons plutôt un objet JavaScript qui contient trois éléments :

  1. outer se réfère au cercle extérieur (bleu à l'occurrence)
  2. medium se réfère au cercle central (rouge à l'occurrence)
  3. inner se réfère au cercle plus petit (jaune à l'occurrence)

Voici le code pour obtenir cet objet :

//Identify the circles
var theCircles = {
    outer: document.querySelector("#outerCircle"),
    medium: document.querySelector("#mediumCircle"),
    inner: document.querySelector("#innerCircle")
}

Une fois les références symboliques aux cercles déclarées, nous pouvons par exemple récupérer des informations sur leurs attributs. Il faut faire attention cependant que, contrairement aux éléments HTML, nous ne pouvons pas utiliser la notation element.attribut. Donc le code theCircles.outer.fill n'est pas valable. Il faut utiliser plutôt la méthode getAttribute(), et par conséquent le code correcte serait : theCircles.outer.getAttribute("fill"). Le voici répété pour les trois cercles dans trois instructions de output à la console :

//Log the color of the circles
console.log(theCircles.outer.getAttribute("fill"));
console.log(theCircles.medium.getAttribute("fill"));
console.log(theCircles.inner.getAttribute("fill"));

Le résultat de l'output est le suivant :

blue
red
yellow

Manipuler les attributs d'un élément SVG avec JavaScript

Selon le même principe de l'exemple précédent, nous pouvons manipuler les attributs d'un élément contenu dans le svg. Au lieu de getAttribute(attribute-name) il faudra par contre utiliser setAttribute(attribute-name, new-value). Cette méthode s'occupe tout simplement de :

  1. définir quel attribut il faut changer
  2. attribuer une nouvelle valeur à cet attribut

Dans l'exemple 00-03 nous utilisons la méthode setAttribute("fill", "new-color") pour modifier la couleur originale des trois cercles. Le code est limité ici au script JavaScript (lignes 68-77) :

//Identify the circles
var theCircles = {
    outer: document.querySelector("#outerCircle"),
    medium: document.querySelector("#mediumCircle"),
    inner: document.querySelector("#innerCircle")
}
//Modify the color of the circles with JavaScript
theCircles.outer.setAttribute("fill", "violet");
theCircles.medium.setAttribute("fill", "green");
theCircles.inner.setAttribute("fill", "black");

En conséquence de ce code qui est exécuté lorsque la page a été affiché dans le navigateur, les trois cercles n'apparaissent pas bleu, rouge et jaune, mais plutôt violet, vert et noir.

Manipuler le SVG à partir d'un événement lié à HTML

Maintenant qu'on sait comment manipuler des attributs des éléments <svg>, on peut effectuer une manipulation en fonction d'un événement déclenché par un élément qui se trouve dans le HTML de la page. Dans l'exemple 00-04 nous utilisons le clique sur un bouton pour changer aléatoirement la couleur des trois cercles.

Pour obtenir ce résultat il nous faut d'abord le bouton dans la page HTML, ligne 35 :

<button class="btn-lg btn-primary" id="switchColorBtn">Random circles' colors</button>

Nous avons attribué un id au bouton afin qu'on puisse l'identifier en JavaScript et lui associer un gestionnaire d'événement à travers la méthode addEventListener() (ligne 83-84) :

//Identify the circles
var theCircles = {
    outer: document.querySelector("#outerCircle"),
    medium: document.querySelector("#mediumCircle"),
    inner: document.querySelector("#innerCircle")
}
//Generate a random rgb color, from http://webdesignnomad.com/snippets/random-color-javascript/
function random_rgb() {
    return 'rgb(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(
        Math.random() * 256)) + ')';
}
//Identify the button and add an event listener
var theButton = document.querySelector("#switchColorBtn");
theButton.addEventListener("click", function () {
    //Modify the color of the circles with JavaScript
    theCircles.outer.setAttribute("fill", random_rgb());
    theCircles.medium.setAttribute("fill", random_rgb());
    theCircles.inner.setAttribute("fill", random_rgb());
});

À la ligne 78 nous avons incorporé une fonction, tiré du site Web design nomad, pour générer aléatoirement une couleur rgb(red, green, blue). Ce code s'occupe tout simplement de générer 3 chiffres aléatoires entre 0 et 255 en correspondance des trois couleurs à mélanger.

Cette fonction est enfin utilisée à l'intérieur du gestionnaire d'événement pour changer l'attribut fill de chaque cercle avec une couleur aléatoire (lignes 86-88).

Manipuler l'HTML à partir d'un événement lié au SVG

Nous pouvons appliquer le principe inverse par rapport à l'exemple précédent pour rendre les éléments svg interactifs, dans le sens où on peut cliquer sur les éléments contenus dans un svg et déclencher des événements qui modifient le svg lui-même, mais aussi d'autres éléments contenus dans la page HTML.

Dans l'exemple 00-05 nous considérons les 3 cercles comme faisant partie d'une cible. Si vous cliquez sur le jaune, vous obtenez 50 points, 25 points pour le rouge et 10 pour le bleu.

Le score est mis à jour directement dans un élément HTML (lignes 38-41) :

<p class="text-center display-4 bg-success text-white p-3">
   Score:
   <span id="myScore">0</span>
</p>

Pour incrémenter le score suite aux cliques sur les cercles, nous avons attribué trois gestionnaires d'événements, un pour chaque cercle :

//Identify the circles
var theCircles = {
    outer: document.querySelector("#outerCircle"),
    medium: document.querySelector("#mediumCircle"),
    inner: document.querySelector("#innerCircle")
}
//Identify the score
var theScore = document.querySelector("#myScore");
//Create a function to update the score
function updateScore(increment) {
    var newScore = Number(theScore.textContent) + increment;
    theScore.textContent = newScore;
}
//Add event listeners to the circles
theCircles.outer.addEventListener("click", function () {
    updateScore(10);
});

theCircles.medium.addEventListener("click", function () {
    updateScore(25);
});

theCircles.inner.addEventListener("click", function () {
    updateScore(50);
});

À l'intérieur de chaque gestionnaire d'événement, nous appelons la fonction updateScore(increment) qui s'occupe de mettre à jour le total et de l'afficher dans le span ainsi qu'il soit visible dans la page.

À noter enfin que dans les déclarations de style CSS de la page, nous avons ajouté le code suivant :

svg circle:hover {
    cursor: pointer;
}

Ceci permet aux utilisateur de s'apercevoir que les cercles sont cliquables, car contrairement à l'élément button utilisé dans l'exemple précédent, le navigateur ne considère pas que les éléments svg sont forcément censés être interactifs. De se fait, nous changeons le type de curseur lorsque la souris passe sur l'un des cercles.

Exemples

Dans cette section, nous proposons quelques exemples qui appliquent les concepts de base expliqués dans la section précédente.

Utilisation d'un SVG externe

Lorsqu'on utilise des SVG plus complexes, qui proposent par exemple plusieurs formes et patterns dont le code peut s'étaler sur plusieurs lignes XML, il n'est pas pratique d'inclure tout ce code à l'intérieur des pages HTML. Dans ce cas, il est préférable d'inclure le SVG en tant qu'élément externe, et le manipuler par la suite avec JavaScript. Pour obtenir cet effet, il faut au moins trois passages :

  1. Appliquer des identificateurs (classes ou IDs) saillants aux éléments, ou groupes d'éléments, contenus dans le SVG.
  2. Inclure le SVG à l'aide de la balise <object type="image/svg+xml" data="path/to/file.svg"></object> et non pas avec la balise <img ...>
  3. Ajouter un gestionnaire d'événement qui attend le téléchargement (i.e. le load) du SVG externe avant de pouvoir le manipuler à travers JavaScript

Nous allons illustrer les trois passages à l'aide d'un exemple pratique que vous pouvez tester directement sous ce lien :

L'application propose le dessin d'un visage avec quelques éléments (chapeau, lunettes, ...). Lorsqu'on clique sur ces éléments, leur nom va s'afficher dans un élément du DOM de la page qui contient le dessin.

Les fichiers nécessaires au fonctionnement de l'application sont contenus dans le dossier :

Appliquer des identificateur aux éléments

Le premier passage nécessaire au fonctionnement de cette application concerne le dessin SVG lui-même. Grâce à des logiciels de dessin comme Inkscape ou Illustrator, il est assez facile d'appliquer des classes ou des IDs aux noeuds du SVG. À la limite, cette étape peut être effectuée également avec un simple éditeur de texte si on arrive à identifier facilement les éléments.

Pour notre exemple, nous avons appliqué des IDs aux différents éléments que nous voulons par la suite rendre interactifs, par exemple :

  • Le chapeau
    <g
           id="hat">
          <rect
             y="281.51608"
             x="67.084908"
             height="81.347412"
             width="615.83014"
             id="rect3392"
             style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:0.6870625px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
          <rect
             y="35.120827"
             x="248.27586"
             height="263.79309"
             width="262.06897"
             id="rect3394"
             style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
        </g>
    
  • Les lunettes
     <g
           id="glasses">
          <circle
             r="77.586205"
             cy="495.46567"
             cx="279.58621"
             id="path4254"
             style="fill:#00ffff;fill-opacity:0.47999998;stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
          <circle
             r="77.586205"
             cy="497.18979"
             cx="460.6207"
             id="path4254-7"
             style="fill:#00ffff;fill-opacity:0.47999998;stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
          <path
             inkscape:connector-curvature="0"
             id="path4273"
             d="m 124.41379,497.18979 c 74.13793,0 77.58621,0 77.58621,0 l 0,0"
             style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
          <path
             inkscape:connector-curvature="0"
             id="path4273-2"
             d="m 544.24138,495.46565 c 74.13793,0 77.58621,0 77.58621,0 l 0,0"
             style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
          <path
             inkscape:connector-curvature="0"
             id="path4290"
             d="m 353.44828,493.74152 c 31.03448,0 29.31034,-1.72414 29.31034,-1.72414"
             style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
        </g>
    
  • etc.

Voir le code complet du SVG

Inclure le SVG dans la page HTML5

Le deuxième passage nécessaire consiste tout simplement à inclure l'élément SVG à l'intérieur de la page HTML5 de l'application. Les deux aspects critiques à ce propos sont les suivants :

  1. Il faut utiliser la balise <object>...</object>
  2. Il faut pouvoir par la suite que JavaScript puisse accéder à cet objet facilement, par exemple grâce à un attribut id="..."

Dans notre exemple, le code qui s'occupe de ces deux aspects se trouve à la ligne 23 de la page HTML5 :

<!-- Include the external SVG -->
<object id="faceSVG" type="image/svg+xml" data="face.svg" style="height: 500px"></object>

L'attribut data="face.svg" s'occupe tout simplement de pointer vers le fichier SVG externe. Dans ce cas, le fichier s'appelle face.svg et se trouve dans le même dossier de la page, autrement il aurait fallu spécifier le bon chemin comme pour tout autre élément externe.

Ajouter un gestionnaire d'événement de type load

Enfin, il faut considérer que le téléchargement du fichier prend du temps et par conséquent que son DOM ne sera pas accessible tout de suite à l'interprète JavaScript. Pour cette raison, il faut d'abord s'assurer que l'élément a été téléchargé grâce à un gestionnaire d'événement de type load.

Voici l'extrait du code de l'application qui permet de comprendre ce mécanisme :

<p>Try to click on the different elements of the face to see their names.</p>
    <!-- Include the external SVG -->
    <object id="faceSVG" type="image/svg+xml" data="face.svg" style="height: 500px"></object>
    <h2 id="feedback">You have not clicked yet!</h2>
    <!-- Add the script -->
    <script>
        //Identify the object as any other HTML element
        var svg = document.getElementById("faceSVG");
        //Identify the output element in the page
        var feedback = document.getElementById("feedback");
        //Since it is an external element, we need to wait for it to be loaded with an event listener
        svg.addEventListener("load", function () {
            //At this point, the DOM of the SVG can be accessed with the .contentDocument property
            var face = svg.contentDocument;
            //Create an array with the different IDs of the elements
            var faceElements = ['hat', 'glasses', 'moustaches', 'beard'];
            //Iterate through the elements
            faceElements.forEach(function (el) {
                //Identify the current element of the iteration
                var current = face.getElementById(el);
                //Associate a mouseover event-listener to change the pointer
                current.addEventListener("mouseover", function () {
                    current.style.cursor = "pointer";
                });
                //Associate a click event-listener to show the feedback
                current.addEventListener("click", function () {
                    feedback.innerHTML = "You clicked on the " + el.toUpperCase();
                });
            });
        });
    </script>

Les deux lignes qui nous intéressent particulièrement sont les lignes 32 et 34 :

  • À la ligne 32, nous appliquons le gestionnaire d'événement de type load à l'élément SVG que nous avons préalablement identifié à la ligne 28
  • À la ligne 34 nous exploitons la propriété .contentDocument qui permet d'accéder au DOM de l'élément externe incorporé dans la page. C'est grâce à ce mécanisme que par la suite nous pouvons identifier les groupes d'éléments avec id hat, glasses, moustaches, etc.

À l'intérieur du gestionnaire d'événement, nous utilisons du code JavaScript de manière itérative sur les éléments interactifs pour leur appliquer à leur tout des gestionnaires d'événements de type 'click'. Ces gestionnaires permettent d'afficher le nom de l'élément dans le paragraphe avec id="feedback".

Exercice de consolidation

Objectif de l'exercice : ajouter la bouche aux éléments interactifs de l'application. Pour ce faire, il faudra :

  1. Modifier le fichier face.svg pour rendre identifiable le(s) noeud(s)
  2. Modifier le code JavaScript pour ajouter cet élément à la liste des éléments interactifs

Bibliographie

  • Drasner, S. (2017). SVG Animations. From Common UX Implementations to Complex Responsive Animation. Sebastopol, CA: O’Reilly Media.
  • Eisenberg, J. D., & Bellamy-Royds, A. (2014). SVG Essentials. Producing Scalable Vector Graphics with XML (2nd ed.). Sebastopol, CA: O’Reilly Media.

Ressources