Scalable Vector Graphics (SVG) est une spécification du World Wide Web Consortium (W3C) qui utilise XML pour décrire une image vectorielle à deux dimentions. Outre produire des images fixes, SVG a aussi été conçu dans l'optique de pouvoir produire des animations. En fait, le standard définit même plusieurs techniques pouvant être utilisées pour animer des images SVG.

Ici, nous allons utiliser JavaScript pour manipuler l'arbre des éléments (DOMdocument object model) du document SVG afin de créer une animation.

Note:

Il existe une grande disparité de support pour SVG entre les différents navigateurs web du marché. Les manipulations décrites ici ont été testées avec Iceweasel/Firefox 3.0.6 sous GNU/Linux Debian, avec le navigateur Gnome Epiphany Web Browser 2.22.3 (toujours sous Debian) et avec Firefox 2.0.0.20 sous MacOS X.

A priori tout navigateur basé sur un moteur Gecko pas trop ancien devrait se comporter de manière identique. Un navigateur Opera, Safari ou autre récent fonctionnera sans doute aussi. Finalement, seuls les inconditionnels d'Internet Explorer risquent d'avoir de réels soucis...

L'image de base

Puisque nous allons utiliser JavaScript pour animer une image SVG, il faut avant toute chose créer cette image initiale.

L'animation qui nous servira d'exemple sera un (magnifique) couché de soleil. Pour commencer, nous allons donc créer le document SVG qui compose la scène initiale. En l'occurence, notre image sera composée d'un fond noir, et d'un cercle rouge:

<svg width="200px" height="150px" xmlns="http://www.w3.org/2000/svg">
    <rect x="0" y="0" height="150" width="200" fill="black" />
    <circle id="sun" cx="100" cy="100" r="50" fill="red" />
</svg>

Si vous chargez ce document dans votre navigateur-web-compatible-SVG préféré vous devriez obtenir une image identique à celle ci-dessus.

L'animation en JavaScript

Moteur d'animation

Toutes les manipulations du document SVG vont maintenant prendre place dans une seule et même fonction JavaScript. Celle-ci sera appelée à plusieurs reprises par un timer et à chaque appel elle jouera une image de l'animation.

Ajoutez donc l'élément script ci-dessous dans votre document SVG:

<svg width="200px" height="150px" xmlns="http://www.w3.org/2000/svg">
    <rect x="0" y="0" height="150" width="200" fill="black" />
    <circle id="sun" cx="100" cy="100" r="50" fill="red" />
 
    <script type="text/ecmascript"><![CDATA[
    frame = 0;
    function doOneStep() {
	if (frame++ <= 100)              /* si l'animation n'est pas terminée */
	    setTimeout(doOneStep, 40);   /* appeler doOneStep dans 40ms */
    }
 
    doOneStep(); /* démarre l'animation */
    ]]>
    </script>
</svg>

Comme vous le constatez, la variable frame sert à savoir à quel point on en est dans l'animation. Et vous voyez aussi que le timer n'est plus armé au delà de la 101ème itération (de 0 à 100 il y a bien 101 images). Autrement dit, notre animation sera composée de 101 images, séparées d'un intervalle de (environ) 40ms.

A ce stade, si vous rechargez l'image, vous risquez d'être déçu: en effet, le moteur d'animation est en place, mais celui-ci n'anime rien pour l'instant! Mais il se fait déjà tard: il est donc temps de faire se coucher le soleil...

Animer le soleil

Cette première vraie animation va se faire en deux étapes. Tout d'abord il va falloir récupérer dans le script l'élément (le cercle) représentant le soleil. Cela se fait avec la méthode DOM standard getElementById qui – comme son nom l'indique – permet de récupérer un élément à partir de son id. Quand à changer la position verticale du soleil pour le faire descendre, cela consiste à changer l'attribut cy de l'élément correspondant à l'aide de la méthode setAttribute.

Au final, voici comment il faut modifier la fonction doOneStep:

function doOneStep() {
	sun = document.getElementById("sun"); /* Récupère l'élément du document dont l'id est 'sun' */
	sun.setAttribute("cy", frame+100);    /* Change la position verticale de cet élément */
 
	if (frame++ <= 100)
	    setTimeout(doOneStep, 40);
    }

A cette étape, vous devriez obtenir une première animation avec un soleil qui descend. Et accessoirement qui sort des limites de l'image – donnant par là même l'impression que notre soleil disparaît derrière l'horizon.

Assombrir le ciel

Peut-être que ce fond noir d'encre vous semble bien triste. Pour ajouter un peu de vie à la scène, nous allons rajouter un ciel bleu par dessus – ciel que nous rendrons progessivement transparent, ce qui aura pour effet de créer un fondu du bleu vers le noir.

Tout d'abord, nous allons ajouter un rectangle bleu figurant le ciel par dessus notre fond noir. Cela se fait en ajoutant un élément rect au document SVG:

<svg width="200px" height="150px" xmlns="http://www.w3.org/2000/svg">
    <rect x="0" y="0" height="150" width="200" fill="black" />
    <rect id="sky" x="0" y="0" height="150" width="200" fill="blue" />
    <circle id="sun" cx="100" cy="100" r="50" fill="red" />

Quand à l'animation à proprement parler, elle prendra place comme il se doit dans la fonction doOneStep, et sera produite en jouant sur l'attribut d'opacité (opacity) du rectangle bleu:

function doOneStep() {
    /* ... */
	sky = document.getElementById("sky");
	sky.setAttribute("opacity", (100-frame)/100); /* L'opacité varie de 1.00 à 0.00 */
 
    /* ... */
    }

Pourquoi ne pas plus simplement changer la couleur du fond à chaque animation?

Ce serait évidemment possible. Néanmoins, en agissant ainsi je prépare la suite. En effet, dans quelques minutes nous ajouterons des étoiles, et ce fondu aura pour effet de donner l'impression qu'elles apparaissent progressivement. Alors que ce sera simplement le rectangle bleu les occultant qui disparaîtra...

Ajouter les étoiles

Le soir, lorsque le soleil se couche, les étoiles paraissent. Et bien, dans notre animation, il en sera de même. Autrement dit, si à l'étape précédente nous avons changé les attributs d'éléments existants, ici, nous allons ajouter de nouveaux éléments à l'animation.

Cette fois encore, le modèle DOM possède tout le nécessaire. Sous la forme des méthodes:

Remarque:

Vous connaissez peut-être la méthode DOM createElement que l'on utilise volontiers quand il s'agit de scripter une page web. createElementNS est sensiblement la même, mais elle nous permet de préciser à quel espace de nommage (namespace) l'élément appartient. Pourquoi? Et bien, cela nous permet de bel et bien créer un élément appartement à SVG. Et pas un élément d'une autre spécification.

Voici le code correspondant à la création d'une étoile:

/* ... */
	star = document.createElementNS("http://www.w3.org/2000/svg", "circle");
	star.setAttribute("fill", "white");
	star.setAttribute("cx", Math.random()*200);
	star.setAttribute("cy", Math.random()*150);
	star.setAttribute("r", 2);

La première ligne de ce fragment de code montre comment créer un élément SVG de type circle. Les autres lignes visent simplement à fixer les attributs de notre nouvel élément. Comme vous le constatez, une étoile est donc juste un cercle blanc d'un rayon de 2 pixels.

Note:

La méthode Math.random me permet de fixer une position aléatoire à chaque étoile.

Si maintenant nous avons créé une étoile, celle-ci ne fait pas pour autant partie de la scène. Il faut l'y insérer. Avec la méthode document.createElementNS. Celle-ci est un peu mal faite (à mon avis), puisqu'elle nécessite pour insérer un élément de préciser devant quel élément on veut ajouter le nouveau venu, mais aussi de disposer de l'élément parent. Ce qui complexifie inutilement le code dans le cas général. Mais faisons contre mauvaise fortune bon coeur et ajoutons le code nécessaire à notre fonction doOneStep:

/* ... */
	scene=sky.parentNode;
	scene.insertBefore(star, sky);

Et voilà: rechargez votre image et constatez avec ébahissement que les étoiles apparaissent en même temps que le soleil se couche.

Le code complet

Pour ceux d'entre-vous qui sont perdus ou ont fait une fausse manipulation, voici le code complet de notre animation:

<svg width="200px" height="150px" xmlns="http://www.w3.org/2000/svg">
    <rect x="0" y="0" height="150" width="200" fill="black" />
    <rect id="sky" x="0" y="0" height="150" width="200" fill="blue" />
    <circle id="sun" cx="100" cy="100" r="50" fill="red" />
 
    <script type="text/ecmascript"><![CDATA[
    frame = 0;
    function doOneStep() {
	sun = document.getElementById("sun"); /* Récupère l'élément du document dont l'id est 'sun' */
	sun.setAttribute("cy", frame+100);    /* Change la position verticale de cet élément */
 
	sky = document.getElementById("sky");
	sky.setAttribute("opacity", (100-frame)/100); /* L'opacité varie de 1.0 à 0.0 */
 
	star = document.createElementNS("http://www.w3.org/2000/svg", "circle");
	star.setAttribute("fill", "white");
	star.setAttribute("cx", Math.random()*200);
	star.setAttribute("cy", Math.random()*150);
	star.setAttribute("r", 2);
 
	scene=sky.parentNode;
	scene.insertBefore(star, sky);
 
	if (frame++ <= 100)
	    setTimeout(doOneStep, 40);
    }
 
    doOneStep(); /* démarre l'animation */
    ]]>
    </script>
</svg>

Comme vous le constatez, en quelques lignes de code, nous sommes arrivés à une animation plutôt satisfaisante. Ainsi, en utilisant des techniques de programmation client-side connues et avec un format standardisé pour décrire les images, le couple JavaScript+SVG est sans doute à envisager si vous avez besoin d'introduire des animations dans un document web. Le gros bémol restant le support pour ces technologies dans les navigateurs. Et en particulier du côté de Microsoft [1].

Mais de toute façon, ce problème de compatibilité est une constante du web – tout autant d'ailleurs avec des technologies propriétaires comme Flash ou JavaFX. N'hésitez donc pas à expérimenter à partir des informations décrites ici. Qui sait si SVG ne pourrait pas résoudre élégamment un problème près de chez vous...

Ressources