Tutoriel SVG dynamique avec DOM
Cet article est en construction: un auteur est en train de le modifier.
En principe, le ou les auteurs en question devraient bientôt présenter une meilleure version.
<pageby nominor="false" comments="false"/>
Introduction
Créer des interactions et animations avec le DOM est plus difficile que SVG dynamique avec SMIL, mais on peut faire ce qu’on veut selon le principe que tous les attributs du DOM sont animables.
Certains types d’interactions peuvent se programmer sans savoir grand chose de la programmation DOM (c’est pareil pour le traitement de formulaires HTML avec Javascript ...)
Voici le principe:
- Dans l’élément qui doit déclencher un script on place un attribut d’événement, par exemple onclick (Voir tutoriel SVG dynamique avec SMIL)
- Cet attribut doit définir un fonction avec un argument
- le nom d’une fonction ECMAScript, par ex: circle_click et qui sera appelée
- un argument avec lequel sera transmis un objet événement, par ex. evt
Un objet événement est dynamiquement crée par le client. Il contient des informations variées, par exemple où l’événement a été déclenché etc.
function circle_click(evt) {
var circle = evt.target;
var currentRadius = circle.getAttribute("r");
<circle onclick="circle_click(evt)" cx="300" cy="225" r="100" fill="red"/>
Exemple : Simple switch avec DOM
- http://www.yoyodesign.org/doc/w3c/svg1/animate.html (original, home de la traduction française de la spécification SVG 1.0)
- http://tecfa.unige.ch/guides/svg/ex/svg-w3c/script01.svg
- http://tecfa.unige.ch/guides/svg/ex/svg-w3c/
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="6cm" height="5cm" viewBox="0 0 600 500"
xmlns="http://www.w3.org/2000/svg">
<desc>Exemple script01 - invoque une fonction ECMAScript a partir d'un evenement onclick
</desc>
<!-- Fonction ECMAScript qui change le rayon a chaque clic -->
<script type="text/ecmascript"> <![CDATA[
function circle_click(evt) {
var circle = evt.target;
var currentRadius = circle.getAttribute("r");
if (currentRadius == 100)
circle.setAttribute("r", currentRadius*2);
else
circle.setAttribute("r", currentRadius*0.5);
}
]]> </script>
<!-- Contour en bleu de l'aire du dessin -->
<rect x="1" y="1" width="598" height="498" fill="none" stroke="blue"/>
<!-- S'execute a chaque clic -->
<circle onclick="circle_click(evt)" cx="300" cy="225" r="100"
fill="red"/>
<text x="300" y="480"
font-family="Verdana" font-size="32" text-anchor="middle">
Cliquer sur le cercle change sa taille
</text>
</svg>
Explications:
- L’exemple implémente un switch (assimilable à un interrupteur de lumière)
- On ajoute à la définition cerle un event-handler. Autrement dit: en cliquant sur le cercle (événement = "onclick"), la fonction circle_click ("event handler") va être activée avec l'argument "evt"
<circle onclick="circle_click(evt) cx="300" cy="225" r="100" fill="red"/>
- La fonction circle_click qu’on a défini va recevoir comme argument un objet événement ( le paramètre evt )
- L’objet evt sait quel objet a été touché par l’événement. Autrement dit, ev.target contient l’objet DOM du cercle et on l’assigne à la variable circle .
function circle_click(evt) {
var circle = evt.target:
.....
}
- Ensuite on demande au cercle son attribut "r" (qui définit le radius)
var currentRadius = circle.''getAttribute("r");
Le code suivant substitue la valeur du "r":
- si le radius == 100, alors on va l’agrandir
- sinon, on va le diminuer
if (currentRadius == 100)
circle.setAttribute("r", currentRadius*2);
else circle.setAttribute("r", currentRadius*0.5);
facile, non ? (ce genre de choses se fait aussi en X3D, en HTML et en VRML)
Introduction au scripting avec DOM
Placement et initialisation du script
Le script doit se trouver dans une section CDATA du code SVG, comme en XHTML. La raison est simple: Pour que le XML soit bien formée, on peut ne peut pas y inclure des éléments utilisant une autre syntaxe.
<script type="text/ecmascript">
<![CDATA[
function circle_click(evt) {
..... }
]]>
</script>
Un script travaille souvent avec des variables globales et il faut faire de sorte à ce que l’initialisation soit faite lorsque l’utilisateur interagit avec la scène.
Il faut explicitement charger une fonction d’initialisation. Voici ma façon de faire:
<svg
<script type="text/ecmascript"> <![CDATA[ // variables globales qu'on utilise dans toutes les fonctions var svgdoc, eye_right, ...... ;
function init (big_bang) {
svgdoc = big_bang.target.ownerDocument; // instance of the document ..... }
<svg onload="init(evt)">
<desc> Ici on insère la véritable scène SVG </desc> </svg>
</svg>
L’élément svg imbriqué déclenche le script init() lorsqu’il est chargé
Le déboggage
Comme actuellement les clients n’ont pas de console sophistiquée, il faut trouver soi-même la source des problèmes .... ceci dit, les nouvelles versions des navigateurs sont mieux faites... (à vérifier, DKS - 22 mars 2011 à 18:27 (CET)).
Une solution est de semer partout dans votre code des "alert" qui produisent des fenêtres pop-up lorsque la ligne est exécutée.
Au lieu de les copier/coller tout le temps on propose la solution suivante:
- Une variable globale debug qu’on peut mettre à 0, 1 ou 2 selon vos besoin.
- Si par exemple un problème est résolu, on met le "debug level" à 2. Si debug==1 on ne voit plus l’alert.
// set to 0 in production, 1 for things to debug, 2 for things fixed
var debug = 1;
Voici une du code pour produire les "alert"
if (debug==2) alert ("The document did load nicely, global variables are defined");
// affiche un string plus une représentation d’un objet
if (debug==1) alert ("button =" + currentButton);
... sans ce genre de stratégie vous allez souffrir, car il est facile de mal interpréter les spécifications du DOM ....
Classes et méthodes SVG DOM et XML DOM 2
Une liste complète des classes et méthodes SVG DOM serait monstrueuse, il existe une vingtaine de "chapitres" avec nombreuses classes et méthodes.
Tout SVG est répliqué dans DOM (donc y compris animations et interactivité)
A titre d’exemple, il y a 13 classes DOM spécifiques à la structure du document SVG: SVGDocument, SVGSVGElement, SVGGElement, SVGDefsElement, SVGDescElement, SVGTitleElement, SVGSymbolElement, SVGUseElement, ....
Sinon, SVG implémente DOM2 et nous allons surtout utiliser cela par la suite
L’interface SVGDocument
- un objet SVGDocument va exister quand l'élément racine de la hiérarchie du document XML est un élément 'svg' (cela correspond à HTMLDocument)
- On peut l’utiliser pour chercher des éléments par leur id par exemple.
svgdoc = big_bang.target.ownerDocument;// instance of the document
eye_right = svgdoc.getElementById ("eyeRightId"); // getElementById()
- Attention: Si votre SVG est imbriqué dans un autre document XML (XHTML par exemple) cet objet n’existera pas ! Par contre, cela marche en HTML5.
L'interface SVGSVGElement
- SVGSVGElement est l'interface pour l'élément 'svg'. Cette interface contient des méthodes utilitaires variées diverses couramment employées, telles que des opérations matricielles et la capacité de contrôler le moment du ré-affichage sur les appareils de rendu visuel....
D. Eléments utiles du XML DOM2
L'interface Document
- L'interface Document représente le document XML entier. Donc soit le document SVG standalone ou encore le document XML ou HTML dans lequel SVG est imbriqué
- Voir aussi: [svg-dyn.htm#62986 C. “Classes et méthodes SVG DOM et XML DOM 2” [32]]
- Voir aussi: http://tecfa.unige.ch/guides/tie/html/php-xml/php-xml.html
L'interface Node
- L'interface Node est le type de données principal pour tous les modèles DOM. Il représente un seul nœud dans l'arbre du document
- Le plupart des classes implémentent la plupart des méthodes (cela veut dire que pour manipuler un élément SVG, vous pouvez utiliser des méthodes "Node").
- Ainsi, les attributs nodeName , nodeValue et attributes forment un mécanisme pour obtenir une information sur un nœud, sans devoir faire appel à des interfaces dérivées spécifiques. (voir exemple suivant)
- Sinon, il existe plein de méthodes pour connaître et manipuler les enfants.
L'interface Element
- L'interface Element représente un élément dans un document XML. Les éléments peuvent avoir des attributs associés.
==
==
getAttribute ("string")
- Ramène une valeur d'attribut par son nom
element = ... // objet qui représente un élément
element. getAttribute("r")
setAttribute ("name", "value")
- Ajoute un nouvel attribut. Si un attribut avec ce nom est déjà présent dans l'élément, sa valeur est changée pour celle du paramètre value
element. setAttribute ("fill", "yellow");
getElementById ("id")
- Retourne l’élément dont l'ID est donné par l'attribut elementId. Si un tel élément n'existe pas, cela retourne null.
- Méthode très importante pour les animations, car elle vous permet d’identifier facilement n’importe quel élément enfant qui a un identificateur (attribut "id").
- Voir [svg-dyn.htm#16360 Exemple 4-3: “Simple interaction avec DOM” [36]]
eye_right = svgdoc.getElementById("eyeRightId");
L’interface Event
- permet de fournir des informations sur un événement, par ex. l’attribut target
function infos(evt) {
var element = evt.target ; .... }
<circle onclick="infos(evt)" .... />
Exemple 4-2: DOM alert
- montre quelques méthodes DOM simples
- http://tecfa.unige.ch/guides/svg/ex/svg-dom/dom-alert.svg (animation)
- http://tecfa.unige.ch/guides/svg/ex/svg-dom/
<script type="text/ecmascript"> <![CDATA[
function infos(evt) {
var element = evt.target ; XML DOM
el_name = element.nodeName ; // XML DOM2
el_attributes = element.attributes; // XML DOM2
circle_size = element.getAttribute("r") // XML DOM2
display_text = "Element name = " + el_name;
display_text += " \nAttributes = " + el_attributes;
display_text += " \nCircle size = " + circle_size;
alert(display_text);
element.setAttribute ("fill", "yellow"); //XML DOM2
}
]]> </script>
<circle onclick="infos(evt)" cx="300" cy="225" r="100" fill="red"/>
4.3 Exemple DOM simple
Exemple 4-3: Simple interaction avec DOM
- http://tecfa.unige.ch/guides/svg/ex/svg-dom/omme-dom1.svg (animation)
- http://tecfa.unige.ch/guides/svg/ex/svg-dom/omme.svg (dessin de départ)
- http://tecfa.unige.ch/guides/svg/ex/svg-dom/
Note sur la fabrication du dessin
- Le dessin a été fait avec le logiciel InkScape (qui est un très bon logiciel gratuit qui produit du SVG statique), mais il ne fait pas (encore) du dynamique
- Il fallait faire 2-3 retouches:
- remplacer des éléments CSS par des attributs XML (je ne sais pas comment accéder facilement à un élément CSS dans une longe chaîne via le DOM)
- changer quelques valeurs (opacité, couleurs)
- corriger qq. bugs mineurs
- changer qq. id pour des raisons pédagogiques
- Il faut identifier les id de certains éléments
- Trop souvent ce type de logiciel produit des path (au lieu d’une cercle par ex.), donc on ne s’y retrouve pas dans le code.
- Toutefois, on cliquant sur l’objet dans l’éditeur on peut le voir dans l’arbre DOM dans l’éditeur XML.
Il suffit de noter l’id sur une feuille de papier ou encore de le changer en une valeur intelligible.
Cet exemple:
- implémente des boutons (à droite) qui changent l’émotion du visage.
- les yeux changent de couleur (on aurait pu faire cela sans DOM)
- différentes "bouches" sont cachées/montrées et pour cela il faut un script à mon avis.
Voici quelques éléments SVG:
- Voici l’oeil gauche produit par Inkscape, vert au départ.
<path
id="eyeLeftId" fill = "#00ff00"
style="fill-opacity:1;stroke:#000000;stroke-width:2.0000000;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
transform="translate(-284.0000,-122.0000)"
d="M 492.00000,311.00000 C 492.00000,330.32997 475.43454,346.00000 455.00000,346.00000 C 434.56546,346.00000 418.00000,330.32997 418.00000,311.00000 C 418.00000,291.67003 434.56546,276.00000 455.00000,276.00000 C 475.43454,276.00000 492.00000,291.67003 492.00000,311.00000 L 492.00000,311.00000 z " />
- Voici la bouche "angry", une simple ligne (cachée au départ)
<path
id="angry" visibility="hidden"
style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:14.173228;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
d="M 139.22652,280.11050 L 339.77901,278.45304" />
- Voici le bouton (rouge) qui déclenche le visage "angry"
<rect
onclick="button_click(evt)"
id="rect1333"
style="fill:#ff0000;fill-opacity:1;stroke-width:14.173228"
y="197.23756"
x="474.03317"
height="79.558014"
width="77.900551" />
Les event handlers
- lorsque l’utilisateur clique sur un des rectangles à droite, un script se déclenche
onclick="button_click(evt)"
Les variables globales du script
// variables globales qu'on utilise dans les 2 fonctions
var svgdoc, eye_right, eye_left, mouth_angry, mouth_happy, mouth_sad ;
La fonction d’initialisation sert à trouver les objets qu’on veut animer
function init (big_bang) {
svgdoc = big_bang.target.ownerDocument; // instance of the document
eye_right = svgdoc.getElementById("eyeRightId"); // getElementById()
eye_left = svgdoc.getElementById("eyeLeftId");
mouth_angry = svgdoc.getElementById("angry");
mouth_happy = svgdoc.getElementById("path1363");
mouth_sad = svgdoc.getElementById("path2168");
}
La fonction button_click
- Fonction qui gère les clics des l’utilisateur sur les rectangles
function button_click(evt) {
var button = evt.target;
var currentButton = button.getAttribute("id");
if (currentButton == "blue_button") {
face_change ("blue", mouth_happy );
}
else if (currentButton == "rect1333") {
face_change ("red", mouth_angry);
}
else {
face_change ("black", mouth_sad);
} }
- La fonction button_click reçoit un objet informatique (le paramètre "evt") qui contient toutes les informations sur le click (et surtout l’objet sur lequel on a cliqué) et qu’on va sauver dans une variable "button" ici.
var button = evt.target;
- la propriété "target" de l’objet "evt" référence l’objet sur lequel on a cliqué
var currentButton = button.getAttribute("id") ;
- ensuite on identifie le nom du bouton sur lequel on a cliqué (on lui demande son "id")
- En fonction de l’id du bouton (donc 1 des 3 rectangles) on appelle une fonction face_change qui va transformer le visage.
face_change ("black", mouth_sad);
- Le premier argument donné est une couleur de cheveux (blue, red ou black).
- Le 2ème est une variable associée à l’objet qu’il faut utiliser pour dessiner la bouche
La fonction face_change
function face_change(eye_color, emotion) {
eye_left.setAttribute ("fill", eye_color);
eye_right.setAttribute("fill", eye_color);
if (debug==2) alert ("mouth emotion =" + emotion);
switch (emotion) {
case mouth_angry:
mouth_angry.setAttribute ("visibility","visible");
mouth_happy.setAttribute ("visibility","hidden");
mouth_sad.setAttribute ("visibility","hidden");
break;
case mouth_happy:
mouth_angry.setAttribute ("visibility","hidden");
mouth_happy.setAttribute ("visibility","visible");
mouth_sad.setAttribute ("visibility","hidden");
break;
case mouth_sad:
mouth_angry.setAttribute ("visibility","hidden");
mouth_happy.setAttribute ("visibility","hidden");
mouth_sad.setAttribute ("visibility","visible");
break;
}
}
- L’instruction switch nous permet en fonction de l’objet "emotion/bouche" transmise de rendre cet objet visible et les autres invisibles
- Note: au lieu de rendre visible/invisible un objet on aurait aussi pu l’enlever/ajouter de la scène
- Avec une balise "<g>" on peut regrouper des enfants (dessin de la "bouche")
- Ensuite, avec le DOM, on peut en ajouter ou enlever
5. Animation avec le DOM
Exemple 5-1: Exemple dom01 de la spécification française
- http://www.yoyodesign.org/doc/w3c/svg1/animate.html
(home de la traduction française de la spécification SVG 1.0) - http://tecfa.unige.ch/guides/svg/ex/svg-w3c/dom01.svg
- http://tecfa.unige.ch/guides/svg/ex/svg-w3c/
A notre avis il faut surtout utiliser cette fonctionnalité (au lieu des balises animation de SVG):
- lorsque certaines types d’animation et d’interpolation n’existent pas, par exemple un rectangle qui se promène sur l’écran au hasard (on ne connaît pas son chemin)
- lorsqu’on utilise un navigateur de type Firefox qui n’implémente pas encore les animations.
(explications à faire)