SVGround : cours SVG

Le DOM de SVG

SVG étend le DOM de base grâce au DOM SVG. Ne cherchez pas, on ne peut l’utiliser qu’avec JavaScript pour le moment, mais de toute façon ça n’a que peu d’intérêt côté serveur. C’est la dernière partie du cours, et vous aller voir qu’on peut faire des choses surprenantes. Néanmoins, il est impossible d’être exhaustif sur le DOM SVG. Si vous voulez vraiment le connaître en profondeur, vous allez être obligé de lire la documentation. Mais rassurez-vous, je vous donnerais les clefs pour bien comprendre celle-ci. Vous trouverez tous les liens vers les parties du DOM SVG de la spécification sur la page de liens.

Les types de base

Commençons par présenter les types de base du DOM SVG (c’est un peu fastidieux je vous prévient). Ces types sont en général renvoyés par certaines fonctions du DOM SVG, c’est pourquoi il faut les étudier avant.

Les deux premiers types, les plus simples, sont les types SVGNumber et SVGLength. Attention, leur valeur s’obtient grâce à la propriété value. Supposons qu’un objet obj soit de type SVGNumber (ou SVGLength), on obtient la valeur de cet objet en écrivant obj.value.

On procède par ordre de complexité et le type suivant est donc le point SVGPoint. Comme tout point (et ce jusqu’à preuve du contraire) il a deux caractéristiques : son abscisse et son ordonnée. On y accède grâce au propriétés x et y. Donc, si un objet obj est de type SVGPoint, on obtient ses coordonnées de cette manière : obj.x pour l’abscisse et obj.y pour l’ordonnée.

Passons à quelquechose de (relativement) plus compliqué : le rectangle ! De type SVGRect, il a 4 propriétés : x, y, width et height qui renvoie des nombres flottants. Attention, une erreur courrante est de penser que cet objet de type SVGRect à quelquechose à voir avec un quelconque élément rect, mais ce n’est pas du tout le cas !

Il peut être utile, et vous verez que ça le sera, de créer un nouvel objet d’un de ces types. On utilise dans ce cas plusieurs méthodes sur l’élément svg : createSVGNumber(), createSVGLength(), createSVGPoint() et createSVGRect().

Ainsi pour créer un point par exemple, on doit faire :

Valeurs de base et animées

On peut deviner facilement le type de chaque élément. Une ellipse est de type SVGEllipseElement, un tracé de type SVGPathElement, un arrêt de couleur (stop) de type SVGStopElement, etc. Ça fonctionne pour tous les éléments traités dans ce cours. L’élément svg est donc de type SVGSVGElement.

Or, presque tous les attributs (mais pas les styles) que nous avons ajoutés à des éléments SVG sont accessibles via le DOM SVG : il suffit d’écrire élément.attribut ! Prenons le cas du rectangle. Observez attentivement sa signature :

La première ligne dit qu’on décrit l’interface SVGRectElement. Les 7 lignes suivantes, après les « : », sont les éléments dont hérite un SVGRectElement. Un SVGRectElement a donc toutes les propriétés et toutes les méthodes de SVGElement, SVGTests, SVGLangSpace, SVGExternalResourcesRequired, SVGStylable, SVGTransformable et events::EventTarget dont les définitions se trouvent autre part dans la documentation ! Parfois c’est un vrai labyrinthe…

Mais le plus intéressant, ce sont les lignes entre accolades. Chacune décrit une propriété (attribute) ou une méthode. Ici, il y a six propriétés. Par exemple, la première ligne entre accolades dit que l’interface SVGRectElement possède la propriété x en lecture seule (impossible à modifier, mot-clé readonly) et de type SVGAnimatedLength. Les autres, c’est pareil mais pour les propriétés restantes.

Vous ne connaissez pas encore les types animés. La plupart des attributs SVG sont animables. Hors, quand un attribut est en cours d’animation, la valeur de son attribut reste la même, du moins si vous la récupérez avec la méthode habituelle getAttributeNS. Grâce au DOM SVG, on peut accéder à deux valeurs d’un attribut : la valeur de base qui est celle de départ et qui n’est pas modifiée pendant l’animation et la valeur d’animation qui est modifiée lorsque l’attribut est animé. Reprenons notre SVGAnimatedLength. Sa signature est :

Les deux propriétés qui respectivement donnent la valeur de base et la valeur d’animation renvoie un objet de type SVGLength qui lui permet d’accéder (enfin !) à la valeur grâce à la propriété value. D’ailleurs cette valeur n’est pas en lecture seule, on peut donc la modifier.

Pour résumer, pour obtenir la valeur d’animation de l’abscisse du centre d’un cerlce, on écrit document.getElementById('monCercle').cx.animVal.value. Exemple avec un rectangle :

<]]> < Valeur de base et valeur d’animation ]]>
Valeur de base et valeur d’animation

Contrôle et gestion des évènements d’animation

Les éléments d’animation animate, animateColor, animateMotion, animateTransform et set peuvent être contrôlées via quatre méthodes.

Les deux premières servent à démarrer une animation lorsque l’attribut begin a la valeur indefinite. Grâce à beginElement, on démarre l’animation sur l’élément visé. Avec beginElementAt(x), l’animation démarrera x secondes plus tard. Ces deux méthodes doivent être appelées sur des éléments d’animation.

Il existe deux fonctions symétriques qui servent à arrêter une animation tout de suite (endElement) ou au bout d’une durée déterminée (endElement(décalage), avec décalage en secondes).

<]]> < Gestion des évènements avec le DOM SVG Lancer Lancer dans 2s Stopper Stopper dans 2s ]]>
Gestion des évènements avec le DOM SVG

En plus des évènements vus dans le chapitre précédent, il en existe trois spécifiques aux animations :

Mais il y a mieux. Via l’élément svg, on peut accéder à quatre autre méthodes très utiles. Ainsi :

<]]> < Gestion des évènements avec le DOM SVG Test Lancer Pause Continuer Stopper ]]>
Gestion des évènements avec le DOM SVG

L’évènement SVGZoom, zoom et translation

Les graphiques SVG sont par défaut agrandissables et translatables via l’interface utilisateur. Avec Opera, ces opérations sont accessibles via le clic droit, ou plus simple en conservant Ctrl enfoncé et en scrollant ou en faisant du drag’n drop.

Lors du zoom ou de la translation, des évènements sont envoyés sur l’élément svg. Lors de la translation, c’est SVGScroll qui est envoyé et lors du zoom, c’est SVGZoom.

Il est possible d’empêcher l’utilisateur d’effectuer ces opérations en plaçant l’attribut zoomAndPan à la valeur disable sur l’élément svg.

En effet, il y a deux manières (au moins) de faire des zooms d’un dessin SVG : en jouant avec la viewBox ou en utilisant les possibilités du DOM SVG.

Le DOM SVG fournit un accès direct en lecture et en écriture au zoom et à la translation, via les propriétés currentScale qui renvoie un flottant et currentTranslate qui renvoie un objet de type SVGPoint (qui a deux propriétés x et y).

Voici un exemple ou on manipule ces trois propriétés en même temps (cliquez pour démarrer l’animation) :

<]]> < Zoom et translation avec le DOM SVG ]]>
Zoom et translation avec le DOM SVG

Il y a une autre manière d’effectuer des zooms : tout simplement en utilisant la viewBox. La méthode est simplissime : il suffit de modifier la viewBox pour montrer la région du dessin que l’on veut montrer…

Ceci permet d’introduire la très pratique méthode getBBox() qui peut s’appeler sur tous les éléments graphiques (donc les tracés path) et qui retourne la plus petite boîte englobant l’élément graphique en question, sous forme d’un objet de type SVGRect.

L’exemple suivant montre comment zoomer sur un pays d’une carte. Vous trouverez des cartes vierges sur Wikimedia.

< image/svg+xmlWorld mapSTyx2007-07-27e ]]>
for(i=0;i qui servira à // animer la viewBox (begin vaut indefinite) zoomAnimElt = document.getElementById('zoom'); // on récupère la boîte englobante boundingBox = evt.target.getBBox(); // on change la valeur de to zoomAnimElt.setAttributeNS(null, 'to', boundingBox.x + ' ' + boundingBox.y + ' ' + boundingBox.width + ' ' + boundingBox.height); // on démarre l’animation zoomAnimElt.beginElement(); }]]>
Zoom avec la viewBox et le DOM SVG

Note : cet exemple ne fonctionne pas avec Firefox ≤ 3.5

Évidemment il s’agit d’un exemple très basique. Il n’est pas possible de dézoomer, etc. Mais ajouter des contrôle n’est pas compliqué du tout. On peut même imaginer l’utilisation de contrôles XHTML via l’élément foreignObject ou inversement dans du SVG inclus en ligne dans du XHTML.

Position sur le dessin

Les évènements relatifs à la souris envoient dans l’objet d’évènement des informations précieuses sur la position du curseur. Les quatre propriétés sont les suivantes :

Ce sont les deux premières propriétés qui sont le plus intéressantes puisqu’il est possible d’obtenir la taille dans les mêmes unités les coordonnées du rectangle dans lequel le dessin est affiché. Pour cela, on utilise la propriété viewport de l’élément svg.

<]]> < Obtenir la position du pointeur sur un dessin ]]>
Obtenir la position du pointeur sur un dessin

Note : cet exemple ne fonctionne pas avec Firefox ≤ 3.5, et le viewport ne semble s’afficher dans Opera que quand le fichier est ouvert en plein écran.

Le texte concernant le viewport ne se met à jour que lorsque la fenêtre change de taille (utilisation de l’évènement SVGResize). Redimensionnez là, ouvrez le SVG en plein écran et recommencez pour bien voir comment évoluent les différentes valeurs.

Manipuler les tracés

Les tracés path font exception à la règle qui voudrait que ses attributs soient directement accessibles via le DOM. En effet, les données d’un path contenues dans son attributs d sont complexes et le DOM SVG met à la disposition du programmeur une API pour manipuler les données d’un tracé. On ne peut donc pas faire path.d comme le montre sa signature :

Pas de panique ! Nous reviendrons sur toutes les méthodes plus tard. Ce qui nous intéresse ici, c’est qu’un tracé (de type SVGPathElement) hérite du type SVGAnimatedPathData (9ème ligne), donc hérite des propriétés et des méthodes de SVGAnimatedPathData. Et c’est bien par ce biais qu’il est possible d’accéder aux informations d’un tracé. Regardons la signature de SVGAnimatedPathData :

Ce type est beaucoup plus simple. À l’image des valeurs de base et animées vu précédemment, on peut accéder aux donnés de base ou animées d’un tracé via les propriétés pathSegList (valeurs de base) et animatedPathSegList (valeurs d’animation). Les deux autres porpriétés, normalizedPathSegList et animatedNormalizedPathSegList sont les mêmes à la différence que les coordonnées relatives des tracés (codés par des lettres en minuscule) sont converties en coordonnées absolues (correspondant aux commandes en majuscule).

Ce n’est pas fini : l’objet renvoyé est de type pathSegList. En continuant ce labyrinthe à travers la documentation, on trouve la signature de SVGPathSegList :

Cet objet a des propriétés qui ressemblent furieusement aux méthodes du DOM standard. En fait, les tracés sont représentés dans le DOM SVG comme une liste d’items (appelés segments). On peut obtenir le nombre de ces segments avec la propriété numberOfItems. Mais on peut aussi manipuler ces items : sélectionner un item, insérer un item, en remplacer un ou encore en retirer un. Pour la suite nous allons juste utiliser la méthode getItem(i) qui renvoie le ième segment.

Nous sommes presque au bout. La méthode getItem(i) renvoie un segment, de type SVGPathSeg. Voici la signature de ce type :

Les lignes commençant par const définissent les différents segments possibles pour la propriété pathSegType. Par exemple, si pathSegType vaut 9 on a affaire à un segment de type PATHSEG_CURVETO_QUADRATIC_REL qui correspond (un peu plus loin dans la documentation) à la lettre q. On peut aussi connaître directement la lettre correspondante avec la propriété pathSegTypeAsLetter.

Récapitulons : pour obtenir le type du ième segment d’un tracé p, il faut écrire : p.pathSegList.getItem(i).pathSegType.

Maintenant que nous connaissons le type de segment, il ne reste plus qu’à accéder et modifier ses propriétés. La liste des propriétés dépend du type de segment, à vous de consulter la spécification. Les différents types de segment sont de la forme SVGPathSegNomDeLaCommande. Un petit exemple : si p.pathSegList.getItem(i).pathSegType renvoie la valeur 9, il s’agit d’un PATHSEG_CURVETO_QUADRATIC_REL dont la signature est :

On peut donc accéder à la propriété x (et la modifier puisqu’il n’est pas écrit readonly) comme ceci : p.pathSegList.getItem(i).x !

Dans l’exemple suivant, on parcourt à intervalle régulier tous les segments et, selon leur type, on change aléatoirement les valeurs de leurs propriétés (cliquez pour démarrer l’animation) :

<]]> < Gestion des tracés avec le DOM SVG ]]>
Gestion des tracés avec le DOM SVG

Revenons à la signature du type SVGPathElement :

Les propriétés commençant par createSVGPathSeg servent à créer des segments pour les insérer dynamiquement dans un tracé avec les méthodes insertItemBefore, replaceItem et appendItem, mais nous n’irons pas plus loin dans ce domaine.

Si on enlève toutes ces fonctions, on a alors :

Les trois dernières méthodes peuvent s’avérer très utiles. La première, getTotalLength(), permet d’obtenir la longueur totale du tracé. La seconde, getPointAtLenght(d), permet d’obtenir le point du tracé à la distance d du point de départ du tracé. Enfin, getSegPathAtLenght(d) renvoie le segment situé à la distance d du point de départ.

Dans l’exemple qui suit, on fait avancer un cercle toutes les 50ms sur un tracé grâce à la méthode getPointAtLenght() :

<]]> < Gestion des tracés avec le DOM SVG ]]>
Gestion des tracés avec le DOM SVG

Texte

Il y a quelques propriétés qui peuvent être intéressante pour la gestion du texte dans le DOM SVG. Il est notamment possible d’obtenir la taille du texte calculée par le DOM. Voici la signature du type SVGTextContentElement dont hérite les éléments text de type SVGTextElement :

La propriété textLength correspond à l’attribut du même type (qui permet de forcer la taille du texte, voir le chapitre y traitant) et ne donne pas la taille du texte. C’est la méthode getComputedTextLength() qui permet d’obtenir la taille d’une ligne de texte. Ça peut être extrèmement utile lorsqu’on désire qu’un texte ne dépasse pas. Même si on peut utiliser l’attribut textLength, le résultat n’est pas toujours convaincant et dans certains cas la taille du texte ne peut de toute façon pas être rétrécie assez.

Les méthodes getStartPositionOfChar(i) et getEndPositionOfChar(i) retournent respectivement la position (sous forme d’un objet de type SVGPoint) juste avant (respectivement juste après) le ième charactère.

Ainsi, dans l’exemple suivant, on détecte les phrases trop longues qui passent sous le rectangle et pour chacune d’entre elle, on parcourt toutes les lettres (on obtient le nombre total de lettre grâce à la méthode getNumberOfChars()) tant que le côté droit de la lettre ne passe pas sous le rectangle. On remplace la fin du texte par trois points. (Comme d’habitude cliquez pour voir le résultat.)

<]]> < Gestion du texte avec le DOM SVG John Smith Alexander Von Bunesdom Bob Durand Maximilien de la Coudunière Jean Edouard Dalembert ]]>
var txtElts = document.getElementsByTagNameNS(svgNS, 'text'); // on les parcourt tous for(i=0;i txt = txtElts.item(i); // le rectangle est à x=200 // le texte commence à x=50 // 148+50 = un peu avant 200 if(txt.getComputedTextLength() > 150) { // dernière lettre avant de passer sous le rectangle var max = 0; // tant qu’il y a encore des lettres et que la lettre se termine // avant l’abscisse x=185 while(max < txt.getNumberOfChars() - 1 && txt.getEndPositionOfChar(max).x < 182) { // on passe à la lettre suivante max++; } // on tronque le texte txt.firstChild.data = txt.firstChild.data.substr(0, max) + '…'; } } }]]>
Gestion du texte avec le DOM SVG

Parmi les autres méthodes disponibles, getExtentOfChar(i) est intéressante. Elle renvoyer un rectangle de type SVGRect qui est le plus petit rectangle entourant la lettre.

Intersection

Pour finir, la cerise sur le gâteau si j’ose dire. Le DOM SVG permet de faire des tests d’intersection et d’inclusion sur des régions rectangulaire.

Attention À l’heure actuelle (mi-2009) seul Batik supporte les méthodes pour que cet exemple fonctionne. Firefox, Opera et Webkit ne montrerons pas l’exemple suivant correctement. Utilisez le navigateur Squiggle utilisant Batik pour visualiser cet exemple. Sous Linux, installez Batik avec votre gestionnaire de paquet puis tapez squiggle http://svground.free.fr/images/cours/svgdom/intersect.svg

L’élément svg permet d’appeler de nombreuses méthodes. Quatre méthodes permettent de tester l’intersection et l’inclusion dans un rectangle :

Dans l’exemple suivant, on fait rebondir de balles dans le rectangle svg et on effectue les tests d’intersection et d’inclusion pour chacune d’entre elles à intervalle régulier, en modifiant la couleur selon le cas. Les deux fonctions que vous verez donnent le même résultat mais en utilisant les différentes méthodes citées ci-dessus.

<]]> < Intersection avec le DOM SVG ]]>
Intersection avec le DOM SVG

(Cliquez pour démarrer l’animation.)

On pourra seulement regretter que la seule forme sur laquelle on peut tester ces propriétés est le rectangle. Espérons qu’une future version de SVG dépasse cette limitation.

Le DOM avec JavaScript
SVG1.2