Faire un morphing svg

Illustration de physique quantique illustrant un morphing svg

Si un site web n’était qu’une succession de pages statiques, textes ou images figés, ça ne serait pas très intéressant. A ce compte là autant lire le journal. Un des intérêts d’une page web c’est qu’on peut faire des animations. Faire bouger des trucs quoi. Un morphing svg peut être une solution élégante.

Il existe principalement trois technologies aujourd’hui pour réaliser des animations :

  • CSS
  • JavaScript
  • SVG

Bon, il y en a d’autres mais je ne parle ici que de celles qui se font en codant. Sinon c’est de la triche.

Dans cet article nous allons voir comment nous pouvons animer une illustration svg pour réaliser un morphing.

Une animation permet d’attirer l’attention de l’internaute

Le preuve, cette phrase est la première que vous avez lue sur cette section de l’article

Morphing svg sur le site ArianeLogiX

Avant d’attaquer le dur, une petite démonstration du truc :

Vous pouvez même essayer en live sur le site lui-même. Comme vous le constatez, la courbe s’aplatit pour devenir la démarque du menu lorsqu’on scrolle vers le bas. C’était une demande spécifique du client qui souhaitait que cette courbe soit un élément graphique fondamental du design. L’animer était une évidence.

Mais alors comment qu’on fait ?

Bon, déjà on dit « comment fait-on ? » et non « comment qu’on fait ». Un peu de tenue tout de même.

Analyse du code SVG initial

La première chose, c’est de dessiner la courbe avec un logiciel de dessin vectoriel qui produit des svg (Illustrator, Inkscape…). Bon, moi j’ai juste demandé à ma graphiste Estelle de le faire. Parce que bon, chacun son métier.

Le gros avantage du svg quand on est développeur, c’est que lorsqu’on l’ouvre dans un éditeur de texte, on accède directement au code qui permet de générer l’image. La courbe précédente, par exemple donne ceci :

<svg version="1.1" preserveAspectRatio="none" 
	 id="menu-curve" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1920 313"
	 style="enable-background:new 0 0 1920 313;" xml:space="preserve">
<sodipodi:namedview  bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview11" inkscape:current-layer="svg9" inkscape:cx="732.53424" inkscape:cy="-90.70606" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="1017" inkscape:window-maximized="1" inkscape:window-width="1920" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:zoom="0.31372206" objecttolerance="10" pagecolor="#ffffff" showgrid="false">
	</sodipodi:namedview>
<g id="Calque_3" transform="matrix(1.7147639,0,0,1.5268621,-14.882273,1.3874478)">

	<path class='st0' id="trace_14" d="

		M 1131.7,205.4

		c -354.9,-8.6 -547.3,-79.6 -688.8,-147.8
		
		c -45,-21.8 -129.9,-63 -212.1,-43.8

		C 155.3,31.3 96.3,102.4 48.9,159.6

		c -14,16.9 -27.3,32.9 -39.4,45.5

		L 8,203.7
		
		c 12,-12.5 25.3,-28.5 39.3-45.4

		C 95,100.9 154.2,29.5 230.4,11.8

		c 82.9,-19.3 168.2,22.1 209.3,42

		l 4.2,2

		c 141.3,68.2 333.5,139 688,147.6

		L 1131.7,205.4

		z" >

Je passe sur les premières balises, ce n’est pas un article sur le svg (pour ça je vous renvoie sur cet article de Developper-Mozila très complet) et je vais me concentrer sur la balise <path>. Qu’y a-t-il dedans ?

C’est assez simple en fait. On retrouve une classe CSS et un ID. Ca va nous permettre de manipuler cette balise en CSS ou Javascript. C’est surtout l’attribut « d » qui est intéressant. C’est l’attribut qui sert à définir le tracé.

Analyse du tracé

Chaque ligne de code décrit un mouvement du tracé. Il faut imaginer un crayon qui se déplace sur une feuille de papier aux coordonnées indiquées. Là je l’ai mis en forme en mettant un mouvement par ligne mais quand on ouvre un fichier .svg pour la première fois c’est souvent un vrai bordel…

Décortiquons chaque ligne pour comprendre.

M 1131.7,205.4

Le M signifie MoveTo, elle indique simplement les coordonnées X et Y du point de départ du tracé. Là, on commence en bas à droite.

c -354.9,-8.6 -547.3,-79.6 -688.8,-147.8

Le « c » indique qu’on veut que le tracé décrive une courbe de Bézier. Les six valeurs qui suivent correspondent aux coordonnées des points nécessaires pour définir la courbe. Vous trouverez les explications détaillées ici.

Détail important :

  • le « c » est en minuscule ce qui signifie que les coordonnées sont relatives au point précédent (des coordonnées 0,0 laisseraient le tracé où il en était mais ne le ramènerait pas en haut à gauche).
  • le même « C » en majuscule signifierait qu’on poursuit le tracé à partir sur un point de coordonnées absolues c’est à dire mesurées par rapport à l’ensemble du dessin.
L 8,203.7

Le « L » est une simple ligne droite qui conduit au point indiqué. remarquez qu’il est majuscule, on est donc en coordonnées absolues. Le même en minuscule un peu plus loin dans le code indique des coordonnées relatives.

z" >

Enfin, on termine avec un « z » qui ramène le tracé au point initial.

L’état final de l’animation

La deuxième étape consiste à déterminer l’état final, c’est à dire le tracé vers lequel on souhaite que le morphing conduise. C’est l’étape la plus délicate. Il y a deux écoles.

Vous êtes graphistes ? Cette méthode est faite pour vous

La première méthode consiste simplement à redessiner la courbe avec le même logiciel de dessin vectoriel. Mais attention ! Vous devez absolument vous débrouiller avec les points déjà définis, interdiction d’en enlever ou d’en ajouter. En effet, si vous le faites, le processeur ne saura pas comment se débrouiller avec les nouveaux points et ça ne marchera pas.

Ribik's cube
Comme au Rubik’s cube : seuls des déplacements de points sont permis, pas de création ni de suppression

Le jeu consiste donc à déplacer les points existant, ce qui définira de nouvelles coordonnées mais sans ajouter de points.

Vous êtes développeur ? Alors vous préférez les chiffres…

C’est cette deuxième méthode que j’ai choisie : jouer avec les valeurs des coordonnées pour comprendre comment ça marche. Ensuite, choisir les valeurs qui permettait d’avoir une ligne droite horizontale.

Par exemple, en modifiant le premier « c » :

c -354.9,-8.6 -847.3,-79.6 -688.8,-147.8

On obtient ceci :

Et ainsi de suite, jusqu’à obtenir la courbe finale désirée. Je vous passe les détails, c’est long à faire (mais formateur). Là il fallait obtenir les coordonnées suivantes :

		M 1131.7,205.4

		c -354.9,0 -547.3,0 -688.8,0
		
		c -45,0 -129.9,0 -212.1,0

		C 155.3,205.4 96.3,205.4 48.9,205.4

		c -14,0 -27.3,0 -39.4,0

		L 8,201.7
		
		c 12,0 25.3,0 39.3,0

		C 95,201.7 154.2,201.7 230.4,201.7

		c 82.9,0 168.2,0 209.3,0

		l 4.2,0

		c 141.3,0 333.5,0 688,0

		L 1131.7,205.4

		z

Il y a beaucoup de zéros qui ramènent les points en bas de la zone de dessin. Logique.

C’est vraiment l’étape la plus difficile. Une fois qu’on a ces coordonnées, c’est de la rigolade.

La balise <animate>

La méthode consiste à ajouter, directement dans la balise <path> que l’on souhaite animer, une nouvelle balise <animate> :

<animate  accumulate="none" additive="replace" attributeName="d" begin="indefinite" calcMode="linear" dur="0.5s" fill="freeze"
id="menu-curve-animation" restart="whenNotActive"


	from="

		M 1131.7,205.4

		c -354.9,-8.6 -547.3,-79.6 -688.8,-147.8
		
		c -45,-21.8 -129.9,-63 -212.1,-43.8

		C 155.3,31.3 96.3,102.4 48.9,159.6

		c -14,16.9 -27.3,32.9 -39.4,45.5

		L 8,203.7
		
		c 12,-12.5 25.3,-28.5 39.3-45.4

		C 95,100.9 154.2,29.5 230.4,11.8

		c 82.9,-19.3 168.2,22.1 209.3,42

		l 4.2,2

		c 141.3,68.2 333.5,139 688,147.6

		L 1131.7,205.4

		z"  

		to="

		M 1131.7,205.4

		c -354.9,0 -547.3,0 -688.8,0
		
		c -45,0 -129.9,0 -212.1,0

		C 155.3,205.4 96.3,205.4 48.9,205.4

		c -14,0 -27.3,0 -39.4,0

		L 8,201.7
		
		c 12,0 25.3,0 39.3,0

		C 95,201.7 154.2,201.7 230.4,201.7

		c 82.9,0 168.2,0 209.3,0

		l 4.2,0

		c 141.3,0 333.5,0 688,0

		L 1131.7,205.4

		z">
			</animate>

Vous le voyez, c’est extrêmement simple, on demande au processeur de passer de la première image « from » à la deuxième « to ». Et voilà, c’est fini. Ou presque. En effet, il reste à paramétrer l’animation; ici, on souhaite :

  • qu’elle commence quand on scrolle vers le bas avec la souris
  • une durée de 0,5 s
  • qu’elle s’arrête dans l’état final après un seul déclenchement

C’est le rôle des attributs, tout en haut :

accumulate="none" additive="replace"

empêchent les animations de se cumuler, sans lui les mouvements de la courbe s’additionneraient.

attributeName="d"

indique quel attribut doit être animé

begin="indefinite"

on ne définit pas tout de suite quand l’animation doit commencer. Ce sera fait en javascript pour l’associer à un événement (scroll vers le bas, ici)

calcMode="linear"

l’animation permet de passer de l’état initial à l’état final suivant un calcul linéaire (les valeurs évoluent à vitesse constante)

dur="0.5s"

l’animation doit durer 0,5s

fill="freeze"

quand on atteint l’état final, on y reste en attendant un nouvel ordre

restart="whenNotActive"

un cycle d’animation ne peut pas redémarrer tant que le précédent n’est pas fini

L’animation retour

On va aller très vite, il suffit de copier-coller la balise <animate> précédente et d’inverser les attributs « from » et « to ». On donnera à la balise un autre ID pour pouvoir la déclencher à un autre instant que la première (sion ça va être le bordel, évdemment) :

<animate  accumulate="none" additive="replace" attributeName="d" begin="indefinite" calcMode="linear" dur="1s" fill="freeze"
 id="menu-curve-animation-back" restart="whenNotActive"
			
	from="

		M 1131.7,205.4

		c -354.9,0 -547.3,0 -688.8,0
		
		c -45,0 -129.9,0 -212.1,0

		C 155.3,205.4 96.3,205.4 48.9,205.4

		c -14,0 -27.3,0 -39.4,0

		L 8,203.7
		
		c 12,0 25.3,0 39.3,0

		C 95,203.7 154.2,203.7 230.4,203.7

		c 82.9,0 168.2,0 209.3,0

		l 4.2,0

		c 141.3,0 333.5,0 688,0

		L 1131.7,205.4

		z"

				to="

		M 1131.7,205.4

		c -354.9,-8.6 -547.3,-79.6 -688.8,-147.8
		
		c -45,-21.8 -129.9,-63 -212.1,-43.8

		C 155.3,31.3 96.3,102.4 48.9,159.6

		c -14,16.9 -27.3,32.9 -39.4,45.5

		L 8,203.7
		
		c 12,-12.5 25.3,-28.5 39.3-45.4

		C 95,100.9 154.2,29.5 230.4,11.8

		c 82.9,-19.3 168.2,22.1 209.3,42

		l 4.2,2

		c 141.3,68.2 333.5,139 688,147.6

		L 1131.7,205.4

		z" >
	</animate>

Le déclenchement de l’animation

C’est là tout le sel de la balise <animate> : on peut déclencher l’animation quand on veut. Ici, on souhaite qu’elle parte quand on scrolle vers le bas depuis le sommet de la page. Ca se fait en javascript en créant la fonction fwfScrollAction() :

var runAnimate = true;
//booléen qui sera passé à false dès que le scroll s'arrêtera

//A chaque fois qu'on scrolle
function fwfScrollAction(){

	//Ne continue pas la fonction pour les smartphones
	if(isMobile)
		return

	if($(window).scrollTop() > 0){

		var animate = document.getElementById('menu-curve-animation');
		//on récupère l'élément à animer (la balise animate du svg)
			if (runAnimate) {//l'animation-aller peut se lancer 
							//s'il n'y en a pas une autres en cours
				animate.beginElement();//lancement de l'animation
				runAnimate = false;//blocage de la prochaine animation-aller
			}

	} else {

		var animateBack = document.getElementById('menu-curve-animation-back');
		animateBack.beginElement();	//l'animation-retour se lance à chaque fois
									//que le scroll remonte en haut
		runAnimate = true;	//on permet alors à une nouvelle
							//animation-aller de se lancer

	}

}

Je vous laisse lire les commentaires directement dans le code. Oui car je suis un développeur prévenant, je commente mon code au cas où quelqu’un d’autre doive l’utiliser. Moi par exemple dans six mois…

Juste une petite précision, la variable globale isMobile sert à tester si le visiteur du site est sur un smartphone ou une tablette. Pour la mettre en place, j’ai utilisé un code comme celui-ci. Elle permet de faire du responsive dans le JS, au même titre que les media queries dans le CSS.

Voilà, il ne reste plus qu’à déclencher la fonction fwfScrollAction() au scroll de la souris :

	//A chaque fois qu'on scrolle
	$(window).scroll(function () {
		fwfScrollAction();
	});

Conclusion

Un boulot vraiment sympa à faire, qui m’a bien fait gamberger et un client très satisfait du résultat. J’ai juste explosé le temps prévu initialement… mais ça c’est un peu toute l’histoire du développement…

Références