Chez Jérémie

Chez Jérémie, parfois c'est sérieux, parfois non !

SVG et HTML5 font bon ménage avec Firefox

- Web - Lien permanent

Pour faire suite à mon précédent article sur SVG, je vous propose de partir explorer la révolution technique que représente la possibilité d'inclure du SVG directement dans un document HTML. Cette possibilité fait partie du futur standard HTML 5. C'est une fonctionnalité qui sera disponible dans Firefox 4 et Internet Explorer 9 dès leur sortie. Alors comment ça marche et qu'est-ce qu'on peut en faire ?

Les premiers seront les derniers

Une fois n'est pas coutume, on va commencer par la fin. Dans cette article je vais vous détailler comment j'ai réalisé la démo suivante qui permet de transformer n'importe quelle image ou vidéo disponible en ligne en un Puzzle jouable :

Pour pouvoir voir et utiliser cette démonstration, vous avez besoin d'un navigateur qui supporte les fonctionnalités suivantes :

  • Le support de SVG directement au sein des documents HTML5
  • Le support de la balise canvas
  • Le support des filtres SVG
  • Le support des balises clipPath et foreignObject de la norme SVG
  • Le support de l'API localStorage et de JSON (ça, c'est un peu la cerise sur le gâteau)

Au moment où j'écris ces lignes, le seul navigateur qui réunisse ces conditions nativement est Firefox 4 beta 2. C'est avec lui que j'ai fait tous mes tests.

Vous pouvez aussi tester cette démo avec Firefox 3.6. Néanmoins, pour cela, vous devez activer le support du parser HTML5. C'est possible en vous rendant à l'adresse about:config, en y cherchant le paramètre html5.enable et en lui donnant la valeur true. Attention, il s'agit d'une version expérimentale du parser HTML5 qui n'est pas nécessairement très stable. Pensez à le désactiver une fois que vous aurez finit de jouer ;) (Ceci dit, personnellement, je n'ai jamais eu de problème avec)

A terme, IE9 supportera toutes ces fonctionnalités. Malheureusement, la Plateform Preview 3 ne supporte pas correctement les filtres et comme je n'ai pas de machine tournant sous Windows 7, je n'ai pas pu tester pour voir ce que ça donnait en l'état.

Concernant Safari, Chrome et Opera, je n'ai pas entendu parler d'un support à court terme de SVG directement au sein des documents HTML5.

Mais pourquoi, John ? Pourquoi ?

Avant de détailler comment j'ai utilisé chacune des technologies mise en œuvre, voyons d'abord les raisons des choix techniques fait ici.

  1. SVG directement utilisé dans un document HTML5
    D'un coté, SVG va permettre de dessiner assez facilement les formes tarabiscotées des pièces du puzzle. D'un autre coté, HTML est plus facile à manipuler dès qu'il s'agit de mettre en œuvre une interface utilisateur. Le plus simple est donc d'inclure l'un dans l'autre. Techniquement parlant, il est plus facile d'intégrer du SVG dans HTML que l'inverse car on n'a (presque) pas à se soucier de la gestion des espaces de nom XML de SVG et de XHTML, le parser HTML5 s'en occupe tout seul.
  2. Le support de la balise canvas
    Prendre une source graphique pour en faire un puzzle requière de la manipuler, à minima pour la découper en morceau représentant les pièces. Dès lors qu'on parle de réaliser des opérations de cette nature, c'est canvas qui est le plus indiqué. En outre, cela permet également de traiter n'importe quelle source graphique supportée par le navigateur en entrée et d'en obtenir une sortie unifiée et manipulable de la même façon, peut importe qu'il s'agisse d'une image ou d'une vidéo (ou même d'une page web quelconque... mais je ne suis pas allé aussi loin ici).
  3. Les filtres SVG
    Les filtres SVG permettent d'appliquer très simplement des effets spéciaux sur n'importe quel élément SVG, et, dans le cas de Firefox, sur n'importe quel élément HTML. En outre, les filtres étant déclaratifs (matérialisé sous forme de balises SVG) il est très facile de les modifier et de les ajuster sans avoir à recourir à de complexes artifices de programmation.
  4. Les balises SVG clipPath et foreignObject
    La balise clipPath permet de créer des formes de découpes applicables à un objet. Par exemple, si vous voulez "percer un trou" dans une image, c'est exactement ce qu'il vous faut. Si vous vouliez faire la même chose avec canvas, ce serait possible, mais au prix d'un intense effort de programmation sans la souplesse de pouvoir changer simplement la forme de votre découpe. La balise foreignObject permet quand à elle d'embarquer n'importe quoi dans du SVG. En effet, si au sein d'un document HTML5 il est possible de mettre des îlots de SVG, ça ne veux pas dire que vous pouvez mettre du HTML n'importe comment au milieu des balises SVG.
  5. L'API localStorage et JSON
    Pas indispensable, mais c'est un moyen simple de pouvoir sauvegarder automatiquement la progression de votre Puzzle.

Bouger ou ne pas bouger, tel est la question

La première chose que j'ai faite c'est de prendre la source graphique qui servira de puzzle et de la redessiner dans un élément canvas. J'utilise cela comme une couche d'abstraction. En effet, une fois la source graphique convertis en un élément canvas, je n'ai plus à m'en soucier et quelques soit les opérations que je ferais par la suite, je me contenterai de manipuler un canvas. Accessoirement, l'opération est d'une trivialité absolue :

monCanvas.getContext('2d').drawImage(maSourceGraphique, 0, 0, maSourceGraphique.width, maSourceGraphique.height);

Néanmoins, ce n'est pas tout à fait suffisant pour gérer les vidéos. En effet, pour que l'élément canvas donne l'impression d'être animé comme la vidéo, il faut redessiner le canvas à intervalle régulière au fur est à mesure de la lecture de la vidéo. Pour cela, il suffit d'utiliser la fonction setInterval pour que la méthode drawImage soit appelée régulièrement.

Une fois que ce premier élément canvas est réalisé, chaque pièce du puzzle comprendra son propre élément canvas dans lequel on dessinera la portion du premier canvas qui lui correspond. Cette double utilisation de l'élément canvas est nécessaire pour 2 raisons :

  1. Question de performance : Sean Christmann a montré dans un article sur l'utilisation des balises video et canvas que l'usage de deux canvas successifs pour réaliser des effets spéciaux sur les vidéos apporte un gain non négligeable vis à vis des performances.
  2. Les limites de l'implémentation de SVG : En théorie, il serait possible de définir un élément canvas réutilisable au sein de SVG. Malheureusement les tests que j'ai pu réaliser mon montrés que ce n'était pas possible. Il faut donc créer un élément canvas distinct pour chacune des pièces.

One piece to rule them all

Chaque pièce du puzzle sera un élément svg à part entière créé sur le même modèle. Cela a plusieurs avantages :

  1. Lors du déplacement des pièces, celles-ci pourront êtres déplacées sur l'intégralité de la surface du document HTML. Si elles avaient été une sous-partie d'un seul élément svg, le déplacement aurait été contraint à la zone visible du document SVG correspondant.
  2. Toutes les pièces reposant sur la même arborescence DOM, il est possible de créer une arborescence générique qui sera clonée autant de fois que nécessaire pour créer toutes les pièces.

L'arborescence DOM d'une pièce de puzzle ressemble à ça :

<svg class="piece" width="155" height="155" x="0" y="0">
    <defs>
        <path id="p1_1" d="M10 10 h100 v26 a23,23 0 1,0 0,48 v26 h-25 a25,25 0 1,1 -50,0 h-25 Z"/>
    </defs>
   
    <clippath id="c1_1">
        <use xlink:href="#p1_1"/>
    </clippath>
   
    <g>
        <g clip-path="url(#c1_1)">
            <foreignobject x="10" y="10" width="125" height="125">
                <canvas width="125" height="125"></canvas>
            </foreignobject>

            <use xlink:href="#p1_1"/>
        </g>
    </g>
</svg>

Quelques explications sur cette arborescence :

  • La balise path est encapsulée dans une balise defs. Grâce à cela la forme de la pièce est masquée et pourra être réutilisée à plusieurs endroit via la balise use. Elle est utilisée une première fois dans la balise clipPath pour définir la forme de découpe de la pièce qui sera appliquée à l'élément canvas. Elle est utilisée une deuxième fois après la balise foreignObject pour pouvoir dessiner un contour autour de la pièce tant que la pièce n'est pas à sa place définitive (pour donner un petit effet de lueur interne sans recourir à un filtre).
  • La balise foreignObject est entourée de deux balises g. La première (le parent direct de la balise foreignObject) permet d'appliquer la forme de découpe en même temps à l'élément canvas et à l'élément use. La deuxième balise g permet d'appliquer un éventuel filtre d'ombre porté. Si vous appliquez le filtre et la découpe sur le même élément g, le filtre sera appliqué avant la découpe. Si comme ici, vous voulez que le filtre s'applique à la forme résultante de la découpe, vous êtes contraint de sur-baliser votre code SVG pour forcer l'ordre d'application des effets.
  • Notez l'espace de nom "xlink" sur l'attribut href de la balise use. L'inclusion direct de SVG dans un document HTML5 simplifie drastiquement l'usage des espaces de nom XML mais ne nous en dispense pas totalement. En effet, même dans ce cas d'utilisation, SVG reste du XML et SVG utilisant le langage XLINK pour définir cet attribut href, il est obligatoire de bien spécifier cet espace de nom. Pour éviter d'avoir à faire des manipulations complexes à ce niveau, HTML5 fournis par défaut le raccourcis "xlink" pour gérer cet espace de nom.
  • Vous noterez que la taille de l'élément svg est plus grande que celle de l'élément canvas. C'est nécessaire car la zone visible de l'élément svg correspond à la portion de la source graphique représentée par l'élément canvas ET les éventuels filtres qui s'y appliqueront pour générer les ombres portées.
  • La valeur de l'attribut d de la balise path peut faire peur. Je ne vais pas vous détailler ici le pourquoi du comment (ça mériterai un article à part entière) mais sachez juste que c'est cette simple ligne qui fait qu'on a un tracé de pièce de puzzle (ici le coin haut-gauche). Si vous regardez la source du puzzle, vous verrez que j'ai regroupé toute l'intelligence de la construction de ce chemin dans une fonction dédiée. N'hésitez pas à vous amuser à la décortiquer.

T'as d'beaux yeux tu sais !

Pour rendre l'expérience de jeux un peu plus fun, on va rajouter quelques effet spéciaux et fonctionnalités qui vont permettre de lui donner un aspect un peu plus "fini".

Pimp my mouse

L'idée quand on créer des pièces de puzzle qu'on peut manipuler à la souris, c'est de pouvoir gérer la sélection de la pièce selon la forme de celle-ci. Le problème, c'est que si on ne fait rien, la souris régis à une zone rectangulaire, même sur les zones transparentes. Heureusement, de ce point de vue, SVG est bien fichue et sait gérer ça sans avoir à recourir à des dizaines de lignes de code. En fait, ça se résume à un peu de CSS :

svg{
    pointer-events : none;
}

svg canvas{
    pointer-events : auto;
}

La propriété pointer-events permet de définir comment la souris va réagir lors de ses interactions avec un élément. Ici on dit que la souris ne doit pas déclencher d'évènement lorsqu'elle interagit avec un élément svg mais par contre elle doit réagir normalement avec les éléments canvas présent dans nos éléments svg. Comme les éléments canvas sont découpés par un clipPath, seul les parties visibles de l'élément canvas réagiront à la souris (ce comportement est sans doute la principale différence entre les balises clipPath et mask de SVG). Notez que d'un point de vu Javascript, même si la souris ne renvoie pas d'évènement lorsqu'elle interagit avec les éléments svg, les évènements renvoyés par la souris sur l'élément canvas remontent normalement vers l'élément svg est peuvent être interceptés à ce niveau là. La propriété pointer-event ne fait que bloquer l'émission des évènements, elle ne bloque pas leur diffusion ni leur exploitation dans le DOM.

Ombrage, ô désespoirs !

Pour donner un peu de relief au puzzle, je me suis amusé à rajouter des ombres portés sur les pièces : une petite ombre porté quand une pièce est posée n'importe où, une grande ombre porté quand la pièce est "soulevée" et transportée et enfin, pas d'ombre du tout une fois que la pièce est à sa place définitive. Chacune des ombres (la petite est la grande) est définie à l'aide d'un filtre SVG distinct pour chacune. Par exemple, la petite ombre est définie comme cela :

<filter id="smallShadow" filterUnits="userSpaceOnUse">
    <feColorMatrix type="matrix" in="SourceAlpha" result="greySource"
                   values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  .6 .6 .6 .6 0"/>

    <feGaussianBlur in="greySource" stdDeviation="2" result="flou"/>
    <feOffset in="flou" dx="1" dy="1" result="Ombre"/>
               
    <feMerge>
        <feMergeNode in="Ombre"/>
        <feMergeNode in="SourceGraphic"/>
    </feMerge>
</filter>

Pour faire simple, ce filtre prend la source alpha (la transparence) de l'élément auquel va s'appliquer le filtre, lui applique une matrice de couleur (feColorMatrix) pour quelle soit noir et légèrement transparente, va la flouter (feGaussianBlur), la décaler (feOffset) vers le bas et la droite et va recombiner le tout (feMerge, feMergeNode) avec la source graphique de l'élément. Je ne vous détail pas plus précisément que ça l'usage des primitives de filtres car cela mériterai un article à lui seul, mais c'est tout ça qui fait qu'on a une ombre de forme arbitraire (contrairement à la propriété CSS box-shadow qui ne produit que des ombres rectangulaires... éventuellement avec des coins arrondis, mais c'est tout).

L'application des filtres se fait très facilement directement via CSS

.shadow > g{
    filter:url(#smallShadow);
}

#drag.shadow > g{
    filter:url(#bigShadow);
}

.lock.shadow > g{
    filter:none;
}

Vous n'avez alors plus qu'à jouer sur les classes est les ID de vos éléments avec Javascript pour appliquer ou non un filtre. Notez que Firefox permet également d'appliquer un filtre SVG à n'importe quel élément HTML. C'est ce que j'utilise pour griser l'aide du puzzle qui n'est qu'un simple élément canvas affiché sur l'aire de jeu auquel j'applique un filtre de matrice de couleur.

Allo ! Caroline ! C'est Roger.

Dernière fonctionnalité, la sauvegarde automatique du puzzle en arrière plan. A chaque fois que vous déplacez une pièce, il y a un appel à l'API localStorage de HTML5 pour enregistrer la position de la pièce. De cette manière, si pour une raison ou une autre vous devez interrompre votre partie, vous pouvez la reprendre plus tard exactement là ou vous vous étiez arrêté.

Pour simplifier les traitements, je stocke toutes les données représentant l'état du puzzle dans un objet spécifique. L'API localStorage stocke les données sous la forme clé/valeur. La norme est assez flou sur le type de donnée qui peuvent être stockées mais Firefox n'accepte que les chaines de caractère comme valeur à stocker donc, pour enregistrer mon objet, je le transforme en chaine en utilisant le support natif de JSON tel que définie dans la norme ECMAScript 5 :

var data = JSON.stringify(this.savedData);

window.localStorage.setItem('currentPuzzle', data);

La récupération des données est tout aussi simple :

var data = window.localStorage.getItem('currentPuzzle');

data = JSON.parse(data);

Parfois, c'est le vil coyote qui gagne

Lorsqu'on manipule des technologies nouvellement implémentées dans des navigateurs qui en sont encore au stade des versions Alpha/Beta, il n'est pas rare que l'on soit confronté à des problèmes. Dans ce cas précis, j'ai travaillé avec Minefield qui est la version non-stable de Firefox (et qui représente au moment ou j'écris ces lignes la version de développement du future Firefox 4) et j'ai été confronté à trois types de problèmes différents.

Is there a Road Runner over here?

Les développeurs de Firefox 4 travaillent actuellement comme des malades sur les performances du navigateur et si l'on peut déjà percevoir une amélioration globale des performances, le travail n'est pas terminé et de nombreuses améliorations sont encore attendues dans les semaines à venir. Malheureusement, tout n'est pas parfait...

Dans le cas qui nous occupe ici, le principal problème de performance provient de l'usage des filtres SVG. Les filtres sont actuellement ultra consommateur de ressources pour pouvoir être calculés. Ils sont tellement gourmands que j'ai très vite mis la possibilité de désactiver les ombres portées sur les pièces. En effet, si vous activez les ombres portées et que vous construisez un puzzle basé sur une vidéo, la lecture de celle-ci sera saccadée et le déplacement des pièces ne sera pas fluide. Le problème sera d'autant plus perceptible si vous créez un puzzle avec de nombreuses pièces (plus de 50) car au problème des filtres vient s'ajouter l'augmentation du nombre de nœuds DOM à gérer ce qui peut très vite plomber l'utilisation du jeu (en particulier lorsque vous déplacez les pièces).

Autre point qui peut vite affecter les performances, l'utilisation de l'API localStorage. L'accès en lecture et en écriture via cette API est prohibitif au niveau performance (un peu moins de 100 milliseconde pour une simple opération d'écriture de chaine de caractère sur ma machine... iiirk !). Ainsi si j'avais voulut enregistrer séparément la position de chaque pièces, il aurait fallut près de 10 secondes pour enregistrer l'état d'un puzzle de 100 pièces ! Soyez donc très prudent si vous utilisez cette API car sinon cela peut vite se voir. Si vous prévoyez de faire beaucoup d'opération via celle-ci, je ne saurait trop vous conseiller soit de les traiter en tache de fond asynchrone via l'utilisation de l'API Web Workers, soit de travailler avec un objet contenant vos données que vous transformerez en chaine JSON avant de l'enregistrer intégralement en une seule fois (c'est cette dernière stratégie que j'ai choisie). Si vous n'y faite pas attention, vous pouvez vous retrouver avec un gel complet de votre page !

Robert ! Il y a un cafard dans la machine.

L'intérêt de réaliser ce genre de démo avec une version de développement d'un navigateur, c'est que ça permet de découvrir des bugs qu'il est possible de faire corriger avant la sortie de la version final du navigateur. Les bugs les plus faciles à corriger sont liés à l'implémentation de la balise SVG foreignObject :

  1. Bug 576986 : la balise foreignObject ne respectait pas les règles d'application des évènements de la souris lorsqu'on lui appliquait une forme de découpe. La correction a été apportée dans les heures qui ont suivit. Waow !
  2. Bug 578309 : La balise foreignObject redéfinissait son propre espace de coordonnées, comme si on y avait appliquée une transformation. La encore, il n'aura fallut que quelques jours pour que ce soit corrigé. J'en profite ici pour adresser un grand merci à Robert Longson qui a travaillé sur ces bugs en particulier et qui fait un énorme travail en ce qui concerne le support de SVG dans Firefox.

Mais il y a aussi quelques bugs plus durs qui vont sans doute être assez délicat à résoudre :

  1. Bug 577824 : Quand on attache un élément HTML à l'arbre DOM du document via Javascript et qu'un filtre s'applique sur cet élément, l'élément n'est pas affiché.
  2. Bug 577464 : Celui là, c'est le plus bizarre de tous, et sans doute le plus difficile à résoudre. Dans certain cas de figure, le rendu des éléments auquel est appliquée une forme de découpe n'est pas correctement réalisé. Il est tellement difficile de produire un cas d'utilisation unitaire que son simple diagnostique risque de prendre du temps. Une des pistes actuellement exploré serait un bug lié à la bibliothèque graphique Cairo qui est utilisée par Firefox pour rendre les éléments visuels.

Allo ! Tonton ? Pourquoi tu tousses ?

Après, au delà des éventuels problèmes d'implémentation, il y a aussi les problèmes liés aux spécifications elles-mêmes. Les spécifications qui tournent autour de HTML5 ne sont pas encore finalisées ou pas forcément adapté à certains cas d'utilisation et l'on tombe parfois sur des perles qui peuvent plonger un web designer dans des affres d'incompréhension. J'en ai découvert deux dans ce gout là. Une dans la spécification de l'API Canvas 2D et une dans la spécification de SVG 1.1.

Canvas pas aller ça monsieur.

En l'état de la spécification, avec la méthode drawImage de canvas, il est possible de dessiner soit une image, soit une vidéo. Quand vous dessinez une image, pas de problème, vous dessinez l'image elle-même. Quand vous dessinez une vidéo, ce qui est dessiné, c'est l'image qui est lut par le lecteur vidéo au moment ou on appel la fonction de dessin. Maintenant, que ce passe-t-il si vous voulez dessiner une image animé (aGIF, APNG, MNG, SVG, etc.) ? Moi, assez naïvement, je me suis dit que ça allait dessiner l'image courante de l'animation au moment ou on appel la fonction de dessin... bref que ça allait ce comporter comme avec une vidéo et c'est d'ailleurs comme cela que se comporte Firefox 3.6. Mais que nenni jeune Padawan ! La spécification dit que dans le cas d'une image animé, ce qui est dessiné, c'est la première image de l'animation et puis c'est tout !

http://www.w3.org/TR/2dcontext/#images

When the drawImage() method is passed an animated image as its image argument, the user agent must use the poster frame of the animation, or, if there is no poster frame, the first frame of the animation.

Et chez Mozilla, ils se sont dit que ce serai une bonne idée de suivre la spécification dans Firefox 4. Résultat des courses, j'ai ouvert un bug en pensant à une régression, mais non, c'est juste qu'ils on fait en sorte d'être respectueux de la spécification.

Alors, là, j'ai envie de dire : "Bravo, c'est bien". Mais d'un autre coté, maintenant, on fait comment pour accéder aux autres images qui composent une image animée ? On est typiquement dans un cas ou la spécification est difficile à comprendre et elle reflète sans doute les capacités des fabriquant de navigateur à gérer ce cas d'utilisation. Mozilla sait comment gérer les images animées dans ce cas de figure, mais se bride du fait de la faiblesse de la spécification sur ce point. C'est dommage.

La spécification n'est pas encore définitive et si vous pensez que ce cas d'utilisation est important, faite le savoir. Pour cela, vous pouvez soit faire du lobbying auprès du HTML Worging Group (bon courage !) soit auprès des fabricants de navigateur qui sont directement parti prenante. La discussion est ouverte chez Mozilla et si, par exemple, vous prenez simplement le temps de voter pour le bug que j'ai ouvert, peut-être que Mozilla considèrera qu'il y a matière à pousser le sujet.

SVG, ou l'art de Se Vautrer la Gueule ?

A la base, j'ai fait une mauvaise interprétation de la spécification SVG alors que je voulais réutiliser un élément foreigObject avec la balise use. J'ai naïvement crus que n'importe quelle enfant légitime de la balise g pouvait être réutilisé via la balise use. Malheureusement, ce n'est pas le cas. La balise use ne peut être utilisée que pour dupliquer des éléments explicitement graphique (ce qui n'est pas le cas de foreignObject) ou certains conteneurs (g, symbol et svg). Néanmoins, comme g est un élément valide pour use, il suffit d'envelopper un élément foreignObject avec un élément g est le tour et joué.

Et là de se poser la question : "Mais pourquoi la norme ne permet pas d'utiliser directement tous les enfants légitimes de g ? Pourquoi faut-il rajouter une balise supplémentaire qui a le gout et l'odeur de l'inutile ?". Bien évidement, il s'agit de questions de web designer, pas d'implémenteur. En effet, il y a fort à parier que lorsque la norme a commencé à être écrite (il y a plus de 10 ans, rappelons-le), les développeurs en charge de sa mise en œuvre ont du rencontrer un problème à ce niveau et faire un arbitrage dans ce sens. Ou bien il y a eu un débat philosophique quelconque a propos de ce qu'est la balise use et personne n'a réussie à challenger cette vision. Franchement, je n'en sais rien mais avec le recul que l'on a désormais, la question aurait le mérite d'être à nouveau posée.

De manière général, ce genre de collision malheureuse entre SVG et HTML5 risque de se représenter ailleurs (essayez de contrôler à l'aide de Javascript une balise HTML video que vous avez cloné via la balise SVG use... vous ne pouvez pas !). En effet, on a d'un coté une norme vieillissante (SVG) et de l'autre une norme toute neuve en construction (HTML5). A bien des égards, SVG a été conçus pour vivre dans un univers statique. A l'inverse, HTML5 redéfinis énormément d'éléments dynamiques au travers de nouvelles API Javascript. De ce point de vue, je ne serais pas surpris que HTML5 redonne un coup de fouet à SVG et il n'est pas exclus que dans les années a venir on voit arriver une norme SVG 2 pour résoudre tous ces nouveaux cas d'utilisation... mais bon, ne soyez pas trop pressé. C'est aussi ça qui fait que Flash à encore de beau jours devant lui.

Le mot de la fin (des haricots)

Voila, je viens de vous arroser avec un article copieusement long et en même temps extrêmement raccourcis. Si vous voulez comprendre plus précisément comme marche quoi, je vous encourage à regarder le code source que j'ai essayé de commenter le plus possible. Ce qu'il faut retenir, c'est que le support de SVG dans HTML est encore quelque chose de très expérimental mais cela donne déjà une bonne idée de ce que peut faire chaque technologie respective et ce que l'on gagne à les mixer.

Si vous voulez aller plus loin, n'hésitez pas à lire les spécifications. Elles peuvent parfois s'avérer très utile. La spécification SVG par exemple comporte de nombreux exemples de code très utile à la compréhension. D'autres ressources très utile également sont les documentations techniques des fabricants de navigateurs. En particulier dans le cas de cette démo, le Mozilla Developper Center, mais aussi MSDN, Opera Dev et le wiki de Webkit.

Quoi qu'il en soit, expérimentez vous même et faite vous votre propre idée. Ces technologies sont toutes assez nouvelles, les implémentations évoluent constamment et le processus de standardisation n'est pas linéaire...mais amusez vous, c'est comme ça que vous apprendrez de nouvelles choses ;)