Pour un sujet de cours autour du SVG et du JavaScript voici un article de remise en forme.
Les bases du SVG
Le SVG est un format de dessin vectoriel, vous pouvez en créer et éditer avec Illustrator, Sketch, Inkscape, mais aussi directement à la main.
Le code du fichier est lisible avec un éditeur de type Brackets ou autres et utilise une base xml. On retrouve donc des balises, des attributs et des choses très similaires au HTML.
Quelques noms de balises :
- svg (la base du document)
- g pour groupe
- path pour chemin
- circle pour cercle
- rect pour rectangle
- line pour ligne
- defs et use pour les symboles
Dans les points importants à connaître, le SVG permet de nommer les éléments via des ID et des Class, ce qui est fortement utile pour GSAP.
Illustrator est une usine à gaz, ses SVG sont parfois problématiques, évitez les fonctions trop complexes (transparence, pathfinder, masque, maillage et effets. Exportez votre travail et observez le code, celui-ci doit être assez lisible. Si cela est nécessaire de faire des effets, faites une recherche sur le MDN de Mozilla pour reproduire les ombres, les dégradés…
Les bases de GSAP
GSAP est une libraire JavaScript. Son usage principal est de réaliser des animations pour le web (et parfois des interfaces). Dans les fonctions proposées, la création d’une timeline virtuelle permet de créer et d’ajuster rapidement des animations complexes. À la manière d’un After Effects, on ajoute des éléments, des transitions, des points et on contrôle l’ensemble avec du code à la place d’une interface.
Avoir des connaissances en JavaScript permet d’aller plus loin, mais créer une simple animation ne demande pas énormément de vocabulaire et grammaire JavaScript.
GSAP avec SVG
Pourquoi du SVG ?
- GSAP peut animer du HTML, mais celui-ci peut avoir ses limites, animer les éléments dans un SVG est bien souvent plus performant.
- Le SVG est parfait pour un résultat lisse, fluide avec des formes complexes.
- Vous connaissez suffisamment Illustrator et le design graphique pour produire de belles formes.
Bouts de code pratique
Savoir utiliser TimelineMax suffit à produire des animations évoluées.
Notamment avec timeline.to(‘sélecteur’,durée en secondes,{propriété à animer : valeur, autre propriété à animer : valeur});
Animer un élément par son ID.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<svg width="800" height="800">
<circle id="cercle" r="30" cx="30" cy="60" />
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script>
tl = new TimelineMax(); // je crée une timeline
tl.to('#cercle', 10, { // j'ajoute une transition 'to' à ma 'tl' pour '#cercle' pendant 10 s
x: 500, // va à droite de 500px (on n'oublie pas la virgule)
y: 200 // va en bas de 200px (pas de virgule pour le dernier)
});
</script>
</body>
</html>
Animer certaines balises.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<svg width="800" height="800">
<circle r="30" cx="30" cy="60" />
<circle r="20" cx="170" cy="60" />
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script>
tl = new TimelineMax();
tl.to('circle', 10, {
x: 30,
y: 20
});
</script>
</body>
</html>
Animer les éléments d’une même class.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<svg width="800" height="800">
<style>.color{fill:#f60}</style>
<circle r="20" cx="30" cy="30" />
<circle class="color" r="20" cx="30" cy="90" />
<circle class="color" r="20" cx="30" cy="150" />
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script>
tl = new TimelineMax();
tl.to('.color', 5, {
x: 300,
fill:'black', // en svg background-color = fill
opacity:0.3
});
</script>
</body>
</html>
D’autres combinaisons sont possibles pour cibler les éléments à animer, on utilise les mêmes sélecteurs qu’en CSS.
Exemples :
- ‘#voiture-verte .roues’ toutes les .roues de mon élément #voiture-verte
- ‘.nuages:nth-of-type(3)’ le 3e élément à avoir la class nuages
- ‘line:nth-child(2n)’ une ligne sur deux
Le chainage.
Les transitions ajoutées à la timeline s’enchainent.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<svg width="800" height="800">
<style>.color{fill:#f60}</style>
<circle r="20" cx="30" cy="30" />
<circle class="color" r="20" cx="30" cy="90" />
<circle class="color" r="20" cx="30" cy="150" />
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script>
tl = new TimelineMax();
tl.to('.color', 1, {
x: 300,
fill: 'black',
opacity: 0.3
});
tl.to('circle', 1, {
x: 30,
fill: '#f60',
opacity: 1
});
</script>
</body>
</html>
Les plugins
Pour répondre à des usages différents, GSAP propose des extensions gratuites et payantes.
DrawSVG permet d’animer un tracé en dessinant une partie ou l’intégralité de celui-ci ; ce plugin est payant mais testable sur Codepen. Si vous l’avez sur votre machine, il doit être dans le même dossier que votre html.
AttrPlugin permet d’animer les attributs d’un élément SVG.
Il est présent par défaut dans cette version :
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
ou il faut l’ajouter avec ce script en plus :
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/plugins/AttrPlugin.min.js"></script>
Les bidouilles de codeurs
Voici une démo avec du code JS avancé pour cloner les nœuds du SVG (nœud = l’élément et ses enfants).
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Tap tap tap</title>
<style>
body{
background: #000;
}
</style>
</head>
<body>
<svg id="root" width="800" height="800">
<defs>
<g id="Port">
<text x="0" y="0" font-family="Verdana" fill="#fff" font-size="15">A</text>
</g>
</defs>
<use x="500" y="500" href="#Port" />
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script>
var svg = document.querySelector('#root');
var symbole = document.querySelector('use');
/* la boucle */
var intervalID = window.setInterval(myCallback, 100);
var count=0;
function myCallback() {
count++;
var clone = symbole.cloneNode('true');
var random_a = Math.random() * 800 - 400; // un chiffre entre -400 et 400
var random_b = Math.random() * 800 - 400;
svg.appendChild(clone);
// V1 pour une animation plus complexe
//tl = new TimelineMax();
//tl.to('use:last-child',2,{x:random_a,y:random_b,opacity:0});
// V2 juste une transition
TweenMax.to('use:last-child',2,{x:random_a,y:random_b,rotation:random_b,opacity:0});
// suppression des clones après 25 boucles
if(count>25){
svg.removeChild(document.querySelector('use'));
}
}
/* BONUS CLAVIER */
document.addEventListener('keydown',function(e){
console.log(e.key);
// on change le texte du symbole
document.querySelector('#Port text').innerHTML = e.key;
})
</script>
</body>
</html>
Variation sans aléatoire
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
body{
background: #000;
width:800px;
margin:20px auto;
}
</style>
</head>
<body>
<svg id="root" width="799" height="800"><!--les valeurs sont ajustées-->
<defs>
<g id="Port">
<text x="3" y="-5" font-family="Verdana" fill="#fff" font-size="15">A</text>
</g>
</defs>
<use x="400" y="400" href="#Port" />
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script>
var svg = document.querySelector('#root');
var symbole = document.querySelector('use');
// la boucle
var intervalID = window.setInterval(myCallback, 16);
var count=0;
var x = 400; // même centre que le symbole
var y = 400;
var mx = 18; // mouvement en x
var my = 21; // mouvement en y
function myCallback() {
count++;
var clone = symbole.cloneNode('true');
x=x+mx; // déplacement
y=y+my;
posx = x - 400; // position par translation
posy = y - 400;
// le rebond
if(posx >= 400 || posx<-400){
mx = mx* -1; // on inverse la direction
}
if(posy >= 400 || posy<-400){
my = my* -1; // on inverse la direction
}
svg.appendChild(clone);
// note : l’usage de GSAP n’est pas forcement justifié dans cette exemple
TweenMax.to('use:last-child',0,{x:posx,y:posy}); // en mettant 3 à la place de 0 c’est amusant
// suppression des clones après x boucles
if(count>1000){
svg.removeChild(document.querySelector('use'));
}
}
/* BONUS */
document.addEventListener('keydown',function(e){
console.log(e.key);
// on change le texte du symbole
document.querySelector('#Port text').innerHTML=e.key;
// l'effet ci-dessous n'est pas extra,
// mais je le laisse en commentaire pour faire des tests
//var tl = new TimelineMax();
//tl.staggerFromTo('use',.3,{rotation:0},{rotation:360},-.05);
})
</script>
</body>
</html>
Centrage.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Tap tap tap</title>
<style>
body{
background: #222;
height:99vh;
margin:0;
}
</style>
</head>
<body>
<svg id="changement" width="100%" height="100%">
<defs>
<g id="Port">
<text x="0" y="0" font-family="Verdana" fill="#fff" font-size="15">A</text>
</g>
</defs>
<rect x="0" y="20" width="800" height="800"/>
<g id="root">
<use x="400" y="400" href="#Port" />
</g>
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script>
var svg = document.querySelector('#root');
var symbole = document.querySelector('use');
/* la boucle */
var intervalID = window.setInterval(myCallback, 100);
var count=0;
var w = window.innerWidth/2;
// je centre le rect et mon groupe #root
TweenMax.set('rect,#root',{x:w-400});
function myCallback() {
count++;
var clone = symbole.cloneNode('true');
var random_a = Math.random() * 2000 - 1000; // un chiffre entre -400 et 400
var random_b = Math.random() * 1000 - 500;
svg.appendChild(clone);
// V1 pour une animation plus complexe
//tl = new TimelineMax();
//tl.to('use:last-child',2,{x:random_a,y:random_b,opacity:0});
// V2 juste une transition
TweenMax.to('use:last-child',2,{x:random_a,y:random_b,rotation:random_b,opacity:0});
// suppression des clones après 25 boucles
if(count>25){
svg.removeChild(document.querySelector('use'));
}
}
/* BONUS CLAVIER */
document.addEventListener('keydown',function(e){
console.log(e.key);
// on change le texte du symbole
document.querySelector('#Port text').innerHTML = e.key;
})
</script>
</body>
</html>
Les bidouilles de graphistes
- Pour masquer un élément on peut mettre l’opacité à 0 (avec GSAP ou Illustrator),
- Si vous ne savez pas cloner en code, clonez dans Illustrator ou Brackets (attention à ne pas cloner les ID)
Les erreurs
- Confusions entre ID, Class et balise,
- Balises non-fermées sur script ou svg…
- Si on ouvre une parenthèse ou un crochet ou un accolade, il faut la fermer,
- Les virgules, les points et les points-virgules sont sources de problèmes.
En cas d’erreur, toujours regarder dans la console, voir la ligne soulignée ou celle avant, relire la doc et repartir d’un exemple !