KineticJS est un framework Javascript qui permet d'utiliser plus facilement l'API Canvas du HTML5. Il étend les possibilités de Canvas : groupes, calques, événements... Il permet également de réaliser des animations très fluides par un système de buffers.
Pour suivre ce tutoriel, vous avez besoin de connaître Javascript. Si ce n'est pas le cas, commencez par vous initier à ce langage, par exemple avec ce tutoriel. Je ne donne aucune indication quant au fonctionnement de ce langage dans ce cours qui est axé uniquement sur l'utilisation de KineticJS .
Premières formes
Dans ce premier chapitre nous allons voir comment démarrer avec KineticJS et dessiner des formes prédéfinies.
Mise en route
Mise en route
Présentation
Canvas est une nouvelle fonctionnalité introduite avec le HTML5. Pour être honnête, c'est Apple qui l'a inventée et elle a été intégrée dans les spécifications du W3C et celles du WhatWG.
Il existe de nombreuses librairies Javascript pour piloter Canvas :
Il me semble que KineticJS , créée et maintenue par Eric Drowell, est l'une des plus efficaces et elle est d'autre part bien documentée.
Le site est plutôt bien fait et comporte de nombreuses démonstrations. Il y a avait un forum dédié que j'aimais bien, mais il a disparu au profit de stackoverflow. Les tutoriels sont bien organisés et couvrent presque toutes les fonctionnalités, on peut toutefois regretter le fait qu'ils soient souvent en retard sur les options proposées.
Vous n'avez pas forcément besoin de savoir manipuler les commandes de base de l'API Canvas pour utiliser KineticJS mais il est évident que vous utiliserez cette librairie de façon beaucoup plus efficace si vous avez des notions concernant cette API.
Installation
Pour utiliser KineticJS vous devez d'abord télécharger la librairie sur le site pour obtenir à coup sûr la dernière version et la placer dans un répertoire de votre site. Au moment où j'écris ce tutoriel, la dernière version est la 5.1.0
Le lien ouvre directement le fichier dans votre navigateur : Ma version utilisée
Il suffit de faire un "Enregistrer sous..." pour placer le fichier dans un répertoire ("js" ou "librairies" ou autre). Pour ce tutoriel je le place dans un répertoire "js". Il suffit ensuite de le référencer sur les pages web :
<script src="js/kinetic-v4.5.4.min.js"></script>
J'utilise la syntaxe HTML5 pour tout ce tutoriel, c'est pour cette raison que je n'ai pas précisé le type de script dans la balise <script> puisque Javascript est le type par défaut.
Il peut également être utile de récupérer le fichier source avec le code en clair et les commentaires pour utiliser la librairie de façon plus efficace. Etant donné le décalage entre la mise à jour de la documentation et les fonctionnalités proposées c'est même presque indispensable. Le mieux est sans doute d'aller voir sur la page de GitHub consacrée au projet.
Structure de base
Pour tous les exemples de ce tutoriel, nous allons avoir besoin d'une structure HTML de base. Ça va être tout simple parce que ça se limite à créer un conteneur avec un identifiant :
<body>
<div id="kinetic"></div>
</body>
Il faut ensuite une structure Javascript de base :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 600,
height: 400
});
var calque = new Kinetic.Layer();
// Ici on dessine sur le calque !
scène.add(calque);
};
Kinetic est l'espace de nom qui permet d'isoler tout ce que nous allons faire avec cette librairie. La première action est de créer une scène (Stage) pour contenir nos créations. Le constructeur de cet objet attend 3 paramètres. Le premier est l'identifiant du contenant HTML, dans notre cas cet identifiant est "kinetic". Les deux paramètres suivants définissent la largeur et la hauteur de la scène.
Il faut ensuite définir au moins un calque pour dessiner :
var calque = new Kinetic.Layer();
Et ajouter ensuite ce calque dans la scène :
scène.add(calque);
Il est aussi judicieux de repérer les limites de la scène en créant une bordure avec une touche de style :
canvas {border-style:solid;}
Mais où se trouve la balise canvas ?
On ne met pas cette balise de façon explicite dans le code HTML mais elle est générée automatiquement par la librairie.
Reprenons tous ces éléments pour obtenir la structure de base de tous nos exemples :
Maintenant nous avons tout ce qui est nécessaire pour commencer à dessiner ;)
Les classes
KineticJS est organisé en classes hiérarchisées. Voici une illustration de sa structure :
Ce diagramme est issu de mes investigations dans le fichier de KineticJS étant donné que la documentation a un peu de retard.
Voici une description sommaire de ces classes :
Classe
Description
Node
C'est la classe principale qui offre les propriétés et méthodes de base pour créer des objets graphiques
Container
Cette classe offre des fonctionnalités pour contenir des Nodes
Group
Permet de regrouper des objets graphiques pour leur appliquer des actions
Stage
Permet de créer une scène pour dessiner et animer
Layer
Pour créer des calques indépendants
Shape
Classe qui permet de dessiner
Path
Pour dessiner en SVG
Sprite
Pour gérer des images regroupées sur une seule image
Rect
Pour dessiner des rectangles
Circle
Pour dessiner des cercles
Ellipse
Pour dessiner des ellipses et donc aussi des cercles
Polygone
Pour dessiner des polygones quelconques
Star
Pour dessiner des étoiles
Line
Pour dessiner des lignes en gérant les liaisons et les extrémités
Text
Pour dessiner du texte
Image
Pour afficher des images
Transition
Une classe isolée pour faire des transitions de position, taille, rotation, etc...
Animation
Une classe isolée pour faire des animations
Il est à noter que le développeur de cette librairie est assez réactif et sort souvent des correctifs, quelques fois un peu rapidement d'ailleurs et je conseille de suivre l'évolution des versions en attendant quelque temps pour que les bugs soient corrigés :p .
Pour commencer tranquillement on va dessiner un rectangle bleu avec la classe Rect. Le constructeur attend au minimum une largeur et une hauteur. Mais si on se contente de ça, on ne voit pas grand-chose à l'écran :D . Voyons le code :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var rectangle = new Kinetic.Rect({
width: 100,
height: 60,
fill: "blue"
});
calque.add(rectangle);
scène.add(calque);
};
C'est la propriété fill qui permet le remplissage. Ici je me suis contenté de la remplir d'un bleu uniforme, on verra qu'on peut également utiliser une image ou des dégradés. Ici j'ai écrit le nom de la couleur, on peut aussi utiliser le code hexadécimal comme pour les propriétés CSS. On peut également utiliser la fonction rgb comme nous le verrons plus loin.
Positionnement
Vous avez sans doute aussi remarqué que le rectangle bleu vient se caler en haut et à gauche du cadre. Pour comprendre ce comportement, il faut déjà connaître le système de coordonnées utilisé. L'origine des axes est justement située en haut et à gauche :
L'axe des X s'étire horizontalement vers la droite et celui des Y verticalement vers le bas.
La référence de positionnement d'un rectangle est également située en haut à gauche. D'autre part les valeurs par défaut de x et y sont à 0. On comprend donc pourquoi notre rectangle bleu va se caler dans le coin haut gauche.
On va maintenant positionner différemment le rectangle en lui fixant des valeurs pour x et y :
var rect1 = new Kinetic.Rect({
x: 150,
y: 100,
width: 100,
height: 60,
fill: "blue"
});
On voit que le trait est géré par la propriété stroke. Ici j'ai choisi la couleur noire (black) et une épaisseur de 6 pixels.
Arrondir les angles
Les rectangles que nous avons dessinés jusqu'à présent ont des angles aigus. Il est possible de les arrondir avec la propriété cornerRadius qui fixe la valeur du rayon de courbure qui est par défaut à 0.
var rect1 = new Kinetic.Rect({
x: 150,
y: 100,
width: 200,
height: 100,
stroke: "#F90",
strokeWidth: 2,
cornerRadius: 20
});
La classe Circle permet évidemment de dessiner des cercles. La dimension d'un cercle est fixée par la longueur de son rayon donnée par la propriété radius. Le trait est géré comme celui des rectangles. Voici un premier exemple :
var cercle = new Kinetic.Circle({
radius: 80,
stroke: "darkgreen",
strokeWidth: 5
});
calque.add(cercle);
On s'attendait à voir un cercle et on en obtient qu'un morceau. Le point de référence est le centre du cercle qui vient tout naturellement se fixer à l'origine des coordonnées. Seule la partie tracée à l’intérieur de la scène apparaît. Pour avoir le cercle entier, nous devons définir la position du centre :
var cercle = new Kinetic.Circle({
x: 140,
y: 120,
radius: 80,
stroke: "darkgreen",
strokeWidth: 5
});
calque.add(cercle);
On voit qu'il suffit de définir la longueur du grand et du petit rayon dans un tableau. On peut aussi utiliser un objet avec les propriétés x et y. Donc ce code est équivalent :
var ellipse = new Kinetic.Ellipse({
x: 140,
y: 120,
radius: {x: 120, y: 80},
stroke: "darkgreen",
strokeWidth: 5
});
Polygones
Triangle
Pour tracer des polygones, il faut utiliser la classe Polygon. Elle permet de dessiner des polygones avec autant de côtés que l'on veut, il suffit de définir la position de chaque point. Voici par exemple un triangle :
var triangle = new Kinetic.Polygon({
points : [{x:40,y:60},{x:180,y:20},{x:240,y:160}],
fill: "#fa0",
stroke: "red",
strokeWidth: 4
});
calque.add(triangle);
On peut dessiner des polygones réguliers avec la classe RegularPolygon. Il faut définir le nombre de côtés avec la propriété sides. Voici par exemple un carré :
var carré = new Kinetic.RegularPolygon({
x: 160,
y: 140,
sides: 4,
radius: 100,
fill: "darkslateblue",
});
calque.add(carré);
On peut considérer le texte comme un ensemble de formes prédéfinies. Il y a une classe Text qui permet de réaliser de belles choses. Voici un premier exemple :
var text = new Kinetic.Text({
x: 40,
y: 30,
text: "Je sais écrire",
fontSize: 26,
fontFamily: "Garamond",
fill: "#c16"
});
calque.add(text);
Dans l'exemple précédent, nous avons rempli chaque caractère avec une couleur unie avec la propriété fill. Il est aussi possible de tracer uniquement le contour avec la propriété stroke :
var text = new Kinetic.Text({
x: 40,
y: 30,
text: "Je sais écrire",
fontSize: 36,
fontFamily: "Georgia",
stroke: "blue",
});
calque.add(text);
On peut se demander ce qu'il se passe si le texte à afficher est plus large que la scène. Voici un exemple :
var text = new Kinetic.Text({
x: 40,
y: 40,
text: "On peut se demander ce qu'il se passe avec un long texte",
fontSize: 24,
fontFamily: "Fraktur",
fill: "darkgreen",
});
calque.add(text);
On se rend compte qu'on perd le texte qui ne rentre pas dans le cadre. On peut arranger ça avec la propriété width qui limite la largeur du texte et provoque un renvoi à la ligne :
var text = new Kinetic.Text({
x: 40,
y: 40,
text: "On peut se demander ce qu'il se passe avec un long texte",
fontSize: 24,
fontFamily: "Fraktur",
fill: "darkgreen",
width: 300
});
calque.add(text);
On a vu qu'on peut créer des paragraphes, on peut aussi aligner le texte avec la propriété align qui est par défaut à left. Les autres valeurs sont center et right. voici le texte précédent centré :
var text = new Kinetic.Text({
x: 40,
y: 40,
text: "On peut se demander ce qu'il se passe avec un long texte",
fontSize: 24,
fontFamily: "Fraktur",
fill: "darkgreen",
width: 300,
align: "center"
});
calque.add(text);
La hauteur des lignes est fixée par défaut à 1.2, on peut la modifier avec la propriété lineHeight :
var text = new Kinetic.Text({
x: 40,
y: 40,
text: "On peut aérer un peu le texte en jouant sur la hauteur des lignes",
fontSize: 24,
fontFamily: "Verdana",
fill: "blue",
width: 300,
lineHeight: 1.6
});
calque.add(text);
Vous avez la possibilité de faire en sorte qu'un texte suive un chemin au format SVG de la forme que vous voulez avec la classe TextPath. Voici un exemple simple :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var textpath = new Kinetic.TextPath({
textFill: 'red',
fontSize: '12',
text: "Un petit texte très sinueux",
data: "M 60,110 C 160,160 120,60 200,60",
scale: 2
});
calque.add(textpath);
scène.add(calque);
};
Si vous vous posez des questions sur la mystérieuse valeur "M 60,110 C 160,160 120,60 200,60", je vous conseille cette lecture bénéfique même si elle n'est pas vraiment très digeste :D .
Maintenant que notre infrastructure est en place nous allons pouvoir commencer à dessiner.
Tracés et images
Maintenant, nous allons voir comment dessiner en traçant des lignes, des courbes et également comment afficher des images.
Lignes
Lignes
Dessiner une ligne
Voici un exemple :
var ligne = new Kinetic.Line({
points: [60, 60, 140, 40, 370, 120, 320, 220],
stroke: "navy",
strokeWidth: 4,
});
calque.add(ligne);
Nous avons vu jusque-là des lignes continues. Il est aussi possible de créer des interruptions pour obtenir des pointillés ou traits d'axe avec la propriété dashArray. Voici un exemple :
Il suffit de définir un tableau de valeurs pour la propriété dashArray. Les valeurs sont par couples de lignes visibles puis invisibles. Par exemple la ligne 1 ci-dessus a des zones visibles de 30 pixels suivies de zones invisibles de 5 pixels. Pour la ligne 2 on a deux couples de valeurs, ce qui permet d'obtenir un effet de trait d'axe. Pour la ligne 3 il a fallu définir un lineCap arrondi pour rendre les points visibles (valeurs à 0).
Images
Images
Exemple de base
Voici un exemple :
var img = new Image();
img.onload = function() {
var image = new Kinetic.Image({
x: 40,
y: 40,
image: img,
width: 120,
height: 120
});
calque.add(image);
scène.add(calque);
};
img.src = "images/kineticjs-bolt-sticker.png";
Lorsqu'il y a plusieurs images à charger il faut prendre la précaution d'attendre le chargement complet des images avant d'initialiser les objets image de KineticJS. Voici un exemple avec 3 images :
// Chargement des images
function load_images(sources, callback) {
var images = new Array();
var loadedImages = 0;
sources.forEach(function(value, index) {
images[index] = new Image();
images[index].onload = function() {
if(++loadedImages >= sources.length) callback(images);
};
images[index].src = value;
});
}
// Initialisation de la scène
function init_scène(images) {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
images.forEach(function(value, index) {
var image = new Kinetic.Image({
x: 40 + index * 144,
y: 80,
image: value,
width: 128,
height: 128
});
calque.add(image);
});
scène.add(calque);
}
// Chargement de la page
window.onload = function() {
var sources_img = [
"images/img01.jpg",
"images/img02.jpg",
"images/img03.jpg",
];
load_images(sources_img, init_scène);
}
Utilisation d'un plugin pour le chargement des images
J'ai trouvé un petit plugin intéressant pour le chargement des images. Vous pouvez le charger ici. Lorsqu'on l'utilise le code devient tout de suite plus léger pour le même résultat que le code précédent :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
scène.add(calque);
var toLoad = [
{id:"myImage1",src:"images/img01.jpg"},
{id:"myImage2",src:"images/img02.jpg"},
{id:"myImage3",src:"images/img03.jpg"},
];
var loader = new Kinetic.Loader(toLoad);
loader.onComplete(function(){
var index = 0;
for(var key in Kinetic.Assets) {
var image = new Kinetic.Image({
x: 40 + index,
y: 80,
image: Kinetic.Assets[key],
width: 128,
height: 128
});
index += 144;
calque.add(image);
}
calque.draw();
});
loader.load();
}
Notez que ce plugin permet aussi de connaître le pourcentage de chargement, le nombre d'images chargées et le nombre total d'images à charger avec son événement onProgress.
Style
Dans cette partie, nous allons voir comment remplir des formes avec des couleurs unies ou des dégradés et comment donner des effets de transparence et des ombrages.
Traits et remplissage
Traits et remplissage
Traits
Nous avons déjà vu comment dessiner des traits. voici un nouvel exemple simple :
var ligne = new Kinetic.Line({
points: [60, 60, 360, 60],
stroke: "#aa4",
strokeWidth: 6
});
calque.add(ligne);
Cette fois on a pas utilisé les propriétés dans le constructeur, mais des setters ensuite dans le code. Pratiquement toutes les propriétés dans KineticJS bénéficient de getters et setters.
Voici un autre exemple :
var ligne1 = new Kinetic.Line({
points: [60, 60, 360, 60],
stroke: "blue",
strokeWidth: 4
});
var ligne2 = new Kinetic.Line({
points: [60, 160, 360, 160],
});
ligne2.setStrokeWidth(ligne1.getStrokeWidth() + 4);
ligne2.setStroke(ligne1.getStroke());
calque.add(ligne1);
calque.add(ligne2);
Il suffit de renseigner la propriété fill avec au choix : un nom de couleur, une valeur hexadécimale ou une valeur obtenue avec la fonction rgb. Voici un nouveau code pour un résultat identique :
var rectangle = new Kinetic.Rect({
x: 50,
y: 50,
width: 140,
height: 60
});
rectangle.setFill("blue");
calque.add(rectangle);
Il faut définir le point de départ avec la propriété start et le point d'arrivée avec la propriété end. Il faut aussi définir les couleurs utilisées et leur positionnement avec une valeur allant de 0 à 1.
On peut obtenir un dégradé vertical tout simplement en choisissant judicieusement les points de départ et d'arrivée :
Ici encore on définit un départ et une arrivée, mais ce n'est plus seulement un point comme pour le dégradé linéaire, maintenant c'est un cercle dont on définit le centre et le rayon.
On peut utiliser les setters pour définir la valeur de la transparence comme je l'ai fait dans cet exemple évoquant la synthèse soustractive des couleurs :
for(i = 0; i < 3; ++i) {
var cercle = new Kinetic.Ellipse({
radius: 80,
opacity: .33
});
switch(i) {
case 0:
cercle.setX(140);
cercle.setY(100);
cercle.setFill("#f00");
break;
case 1:
cercle.setX(240);
cercle.setY(100);
cercle.setFill("#0f0");
break;
case 2:
cercle.setX(190);
cercle.setY(200);
cercle.setFill("#00f");
}
calque.add(cercle);
}
Cette fois on a pas utilisé les propriétés dans le constructeur, mais des setters ensuite dans le code. Pratiquement toutes les propriétés dans KineticJS bénéficient de getters et setters.
Voici un autre exemple cette fois avec un setter qui permet de définir simultanément x et y :
var poly1 = new Kinetic.RegularPolygon({
x: 100,
y: 100,
sides: 8,
radius: 60,
strokeWidth: 10
});
var poly2 = new Kinetic.RegularPolygon({
sides: 3,
radius: 60,
strokeWidth: 10
});
poly2.setPosition(poly1.getX() + 160, poly1.getY() + 10);
calque.add(poly1);
calque.add(poly2);
Mais évidemment cette propriété prendra tout son sens avec les animations.
Rotation avec offset
Les rotations que nous avons faites jusque-là étaient centrées sur le point de référence de la forme, pour une ellipse c'est le centre, pour un rectangle ce serait le coin supérieur gauche. Il y a la propriété offset pour décaler ce point de référence, c'est un tableau avec les valeurs x et y de décalage :
Remarquez que si on définit une seule valeur pour l'offset, et non plus un tableau de deux valeurs comme précédemment, elle est prise en compte pour x et y.
Dimension
Principe de base
Voici un premier exemple :
for(var i = 0; i < 1; i += .12) {
calque.add(new Kinetic.Rect({
x: 90 + i * 80,
y: 40 + i * 80,
width: 150,
height: 150,
fill: "brown",
scale: i
})
);
}
scène.add(calque);
Ici j'ai augmenté progressivement la taille d'un rectangle en le décalant, ce qui crée cette forme en créneaux. La propriété scale attend soit une valeur unique, auquel cas elle affecte le rapport de proportion aux deux dimensions, soit un tableau indiquant le rapport pour x et y.
Changement dynamique
On peut agir de façon dynamique sur cette propriété (comme sur toutes les autres) avec un setter. Voici un exemple :
<script>
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var rectangle = new Kinetic.Rect({
x: 90,
y: 40,
width: 300,
height: 200,
fill: "orange"
});
calque.add(rectangle);
scène.add(calque);
var petit = document.getElementById("petit");
var moyen = document.getElementById("moyen");
var grand = document.getElementById("grand");
petit.onclick = function() {
rectangle.setScale(.2);
calque.draw();
};
moyen.onclick = function() {
rectangle.setScale(.5);
calque.draw();
};
grand.onclick = function() {
rectangle.setScale(1);
calque.draw();
};
};
</script>
</head>
<body>
<div id="kinetic"></div>
<input id="petit" type="button" value="Petit">
<input id="moyen" type="button" value="Moyen">
<input id="grand" type="button" value="Grand">
</body>
Remarquez qu'il faut utiliser la méthode draw du calque pour actualiser la modification en redessinant le rectangle.
Le point point de référence pour le redimensionnement est le même que pour la position. Pour obtenir un effet différent, par exemple centrer le redimensionnement sur le centre du rectangle il faut jouer sur la propriété offset. Voici le même exemple avec un offset qui permet un centrage de l'effet :
var rectangle = new Kinetic.Rect({
x: 240,
y: 140,
width: 300,
height: 200,
fill: "orange",
offset: [150, 100]
});
J'ai ajouté un offset à l'objet pour avoir comme point de référence la pointe de la tige. Ensuite je crée 8 objets en changeant la rotation de 45 degrés pour obtenir ce dessin symétrique. Pour l'esthétique je génère aléatoirement la couleur du disque. On voit ici l'intérêt du groupement qui permet d'appliquer un effet à plusieurs formes avec un code concis.
TP
Vous avez désormais tout en main pour faire quelques TP.
J'ai ajouté un peu de style au canvas pour l'esthétique.
Bien sûr ce n'est qu'un exercice de style parce qu'il n'est pas vraiment nécessaire d'utiliser des objets pour des éléments graphiques qui doivent rester immobiles. Il est alors évidemment bien plus simple d'utiliser une image :
<style>
canvas {
border-style:solid;
border-color:#aaa;
box-shadow: 5px 5px 3px #999;
}
</style>
<script>
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var img = new Image();
img.onload = function() {
var image = new Kinetic.Image({
image: img
});
calque.add(image);
scène.add(calque);
};
img.src = "images/img01_47.png";
};
</script>
Avec évidemment une génération aléatoire des positions, dimensions et couleurs pour obtenir un effet réaliste et varié. Rafraîchissez l'écran pour obtenir de nouvelles compositions.
Ne sautez pas sur ma solution tout de suite, le but est de vous entraîner à utiliser la librairie.
function getColor() {
return "rgb(" + getInteger(256) + "," + getInteger(256) + "," + getInteger(256) + ")";
}
function getInteger(valmax) {
return Math.floor(Math.random() * (valmax + 1));
}
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
// Fond vert
var fond = new Kinetic.Rect({
width: scène.getWidth(),
height: scène.getHeight(),
fill: "#141",
});
calque.add(fond);
// Génération des fleurs
for(var i = 0; i < 20; ++i) {
var groupe = new Kinetic.Group({
x: getInteger(400) + 50,
y: getInteger(180) + 30,
});
(function() {
// Tige
var tige = new Kinetic.Line({
points: [0, 0, 20 - getInteger(40), 120],
stroke: "black",
strokeWidth: 4
});
groupe.add(tige);
// Pétales
var couleur = getColor();
for(var j = 0; j < 360; j += 30) {
var pétale = new Kinetic.Ellipse({
radius: [36, 8],
fill: couleur,
rotationDeg: j,
offset: [14, 0]
});
groupe.add(pétale);
}
// Pistil
var pistil = new Kinetic.Ellipse({
radius: 15,
fill: getColor(),
});
groupe.add(pistil);
groupe.setScale(Math.random() * .7 + .3);
})();
calque.add(groupe);
}
scène.add(calque);
};
Je ne commente pas ce code, je vous laisse l'analyser et comparer avec ce que vous avez produit.
Interactions
Nous allons voir à présent comment agir, avec la souris, sur les formes que nous avons dessinées.
Evénements
Evénements
La classe Node de KineticJS est équipée pour gérer les principaux événements de la souris :
Evénement
Description
click
Se produit lorsqu'on clique sur l'élément associé à l'événement.
dblclick
Se produit lorsqu'on fait un double clic sur l'élément associé à l'événement.
mousedown
Se produit lorsqu'on appuie sur le bouton sur l'élément associé à l'événement.
mouseup
Se produit lorsqu'on relâche le bouton sur l'élément associé à l'événement.
mouseover
Se produit lorsqu'on positionne le curseur sur l'élément associé à l'événement.
mouseout
Se produit lorsque le curseur quitte l'élément associé à l'événement.
mousemove
Se produit lorsque le curseur se déplace sur l'élément associé à l'événement.
mouseenter
Se produit lorsque le curseur se place sur l'élément associé à l'événement.
mouseleave
Se produit lorsque le curseur quitte l'élément associé à l'événement.
Mais c'est quoi la différence entre mouseout et mouseleave ?
Voilà une bonne question, vous avez une excellente réponse ici. Remarquez que c'est la même chose pour les événements mouseover et mouseenter. Tant que vous n'avez pas d'éléments imbriqués, le fonctionnement est identique, mais dès que vous en avez alors les événements mouseenter et mouseleave sont vraiment très pratiques ;) .
Elle permet également de gérer les événements des terminaux mobiles comme les smartphones et les tablettes :
Evénement
Description
touchstart
Se produit lorsque le doigt est placé sur l'élément associé à l'événement.
touchmove
Se produit lorsque le doigt est déplacé sur l'élément associé à l'événement.
touchend
Se produit lorsque le doigt est retiré de l'élément associé à l'événement.
tap
Se produit lors d'un toucher rapide sur l'élément associé à l'événement.
dbltap
Se produit lors d'un double toucher rapide sur l'élément associé à l'événement.
On peut facilement gérer ces événements sur les formes dessinées.
Mise en place
La mise en place de l'écoute d'un événement se fait simplement. Il suffit d'utiliser la méthode on et de renseigner les deux paramètres : le type d'événement à écouter et une fonction à exécuter lorsque cet événement survient. Voici un exemple simple où on détecte le clic de la souris sur un disque coloré :
window.onload = function() {
function setText(text, color) {
Texte.setText(text);
Texte.setTextFill(color);
calque.draw();
}
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Texte = new Kinetic.Text({
x: 10,
y: 10,
text: "Cliquez sur le disque rouge",
fontSize: 18,
fontFamily: "verdana",
textFill: "grey"
});
var disque = new Kinetic.Ellipse({
x: 250,
y: 150,
radius: 70,
fill: "red",
});
disque.on("click", function() {
setText("On a cliqué sur le disque rouge !", "red");
setTimeout(function(){
setText("Cliquez sur le disque rouge", "grey");
}, 2000);
});
calque.add(disque);
calque.add(Texte);
scène.add(calque);
};
On est pas limité à un seul événement sur une forme. Pour en gérer plusieurs c'est tout simple, il suffit de les inscrire en les séparant par un espace. Voici l'exemple d'un disque qui change de couleur sur mousedown et mouseup :
function getColor() {
return "rgb(" + getInteger(256) + "," + getInteger(256) + "," + getInteger(256) + ")";
}
function getInteger(valmax) {
return Math.floor(Math.random() * (valmax + 1));
}
window.onload = function() {
function setText(text, color) {
Texte.setText(text);
Texte.setTextFill(color);
calque.draw();
}
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Texte = new Kinetic.Text({
x: 10,
y: 10,
text: "Cliquez sur le disque, il change de couleur sur mousedown et mouseup",
fontSize: 20,
fontFamily: "verdana",
fill: "grey",
lineHeight: 2,
width: 480,
align: "center"
});
var disque = new Kinetic.Ellipse({
x: 250,
y: 170,
radius: 70,
fill: getColor(),
});
disque.on("mousedown mouseup", function() {
disque.setFill(getColor());
calque.draw();
});
calque.add(disque);
calque.add(Texte);
scène.add(calque);
};
Cette possibilité est utile pour créer des applications qui doivent fonctionner sur écran normal et terminal mobile en associant par exemple mousedown et touchstart.
Supprimer un événement
La suppression d'un événement est aussi aisée que sa mise en place, il suffit d'utiliser la méthode off en indiquant quel événement on veut supprimer. Voici un exemple avec un disque que l'on peut cliquer 3 fois. Au bout de ces trois clics, l'événement est supprimé :
window.onload = function() {
function setText() {
var text = "Cliquez sur le disque rouge\nVous avez encore " + i + " possibilité";
if(i > 1) text += "s";
text += "...";
Texte.setText(text);
disque.setFill("green");
calque.draw();
}
var i = 3;
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Texte = new Kinetic.Text({
x: 10,
y: 10,
text: "Cliquez sur le disque rouge\nVous avez encore 3 possibilités...",
fontSize: 20,
fontFamily: "verdana",
fill: "grey",
lineHeight: 2
});
var disque = new Kinetic.Ellipse({
x: 250,
y: 150,
radius: 70,
fill: "red",
});
disque.on("click", function() {
if(--i) setText();
else {
disque.off("click");
Texte.setText("Vous ne pouvez plus cliquer !");
disque.setFill("red");
calque.draw();
}
});
calque.add(disque);
calque.add(Texte);
scène.add(calque);
};
La suppression totale d'un événement n'est pas toujours judicieuse. Parfois on a juste besoin de suspendre l'écoute pour la réactiver par la suite. La propriété listening nous permet de réaliser cela, il suffit de la renseigner avec la valeur true pour activer l'événement et la valeur false pour le désactiver. Voici un exemple avec l'événement mouseover qui permet de changer la couleur d'un disque lorsque le curseur de la souris passe dessus. Un clic sur le triangle situé à côté permet de désactiver et activer cet événement :
function getColor() {
return "rgb(" + getInteger(256) + "," + getInteger(256) + "," + getInteger(256) + ")";
}
function getInteger(valmax) {
return Math.floor(Math.random() * (valmax + 1));
}
window.onload = function() {
function setText(text) {
Texte.setText(text);
calque.draw();
}
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var état = true;
var calque = new Kinetic.Layer();
var Texte = new Kinetic.Text({
x: 10,
y: 10,
text: "Passez sur le disque pour changer sa couleur et cliquez sur le triangle pour désactiver l'événement",
fontSize: 18,
fontFamily: "verdana",
fill: "grey",
lineHeight: 2,
width : 480
});
var disque = new Kinetic.Ellipse({
x: 150,
y: 180,
radius: 70,
fill: getColor(),
});
var triangle = new Kinetic.RegularPolygon({
x: 350,
y: 200,
sides: 3,
radius: 90,
fill: "red",
});
disque.on("mouseover", function() {
disque.setFill(getColor());
calque.draw();
});
triangle.on("click", function() {
état = !état;
disque.setListening(état);
if(état) setText("Passez sur le disque pour changer sa couleur et cliquez sur le triangle pour désactiver l'événement");
else setText("L'événement est désactivé");
});
calque.add(disque);
calque.add(triangle);
calque.add(Texte);
scène.add(calque);
};
window.onload = function() {
function setText() {
var mousePos = scène.getMousePosition();
var text = "Le curseur de la souris est en X = " + mousePos.x + " Y = " + mousePos.y;
Texte.setText(text);
calque.draw();
}
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Rectangle = new Kinetic.Rect({
width: scène.getWidth(),
height: scène.getHeight(),
fill: "#ade",
});
var Texte = new Kinetic.Text({
x: 10,
y: 10,
text: "Bougez la souris",
fontSize: 18,
fontFamily: "verdana",
fill: "grey",
lineHeight: 2,
width: 480,
align: "center"
});
scène.on("mousemove", function() {
setText();
});
calque.add(Rectangle);
calque.add(Texte);
scène.add(calque);
};
Remarquez que dans l'exemple précédent j'ai créé un rectangle de la dimension de la scène pour faire fonctionner l’événement, en effet par défaut la détection se fait sur les formes, il faut donc en créer une. Pour déterminer la position de la souris sur une forme de dimension plus réduite que la scène, il faut ajuster avec l'offset de positionnement :
window.onload = function() {
function setText() {
var mousePos = scène.getMousePosition();
var x = mousePos.x - Rectangle.getX();
var y = mousePos.y - Rectangle.getY();
var text = "Le curseur de la souris est en X = " + x + " Y = " + y;
Texte.setText(text);
calque.draw();
}
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Rectangle = new Kinetic.Rect({
x: scène.getWidth() / 4,
y: scène.getHeight() / 4,
width: scène.getWidth() / 2,
height: scène.getHeight() / 2,
fill: "#ade",
});
var Texte = new Kinetic.Text({
x: 10,
y: 10,
text: "Bougez la souris",
fontSize: 18,
fontFamily: "verdana",
fill: "grey",
lineHeight: 2,
width: 480,
align: "center"
});
Rectangle.on("mousemove", function() {
setText();
});
Rectangle.on("mouseout", function() {
Texte.setText("Curseur en dehors du rectangle");
calque.draw();
});
calque.add(Rectangle);
calque.add(Texte);
scène.add(calque);
};
Vous vous rendez compte que le clic fonctionne bien, mais on détecte aussi les zones transparentes !
Glisser-déposer
Glisser-déposer
Voyons maintenant un outil très pratique : le glisser-déposer, autrement appelé drag-and-drop par nos amis anglophones. C'est une procédure qui permet de manipuler visuellement des éléments à l'écran et de gérer ce déplacement avec des événements appropriés.
Activation simple
Il y a la propriété draggable pour activer le glisser-déposer. Par défaut elle a la valeur false, mais on peut définir la valeur true pour la rendre opérationnelle. Voici un exemple avec une génération aléatoire de 10 étoiles toutes "draggables" et un peu transparentes pour faciliter la visualisation :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
for(var i = 0; i < 10; ++i) {
var étoile = new Kinetic.Star({
x: Math.random() * scène.getWidth(),
y: Math.random() * scène.getHeight(),
numPoints: 5,
innerRadius: 40,
outerRadius: 80,
fill: '#823',
stroke: '#612',
opacity: 0.7,
strokeWidth: 10,
draggable: true,
scale: (Math.random() * .8) + .2,
rotationDeg: Math.random() * 180
});
calque.add(étoile);
}
scène.add(calque);
};
Comment déplacer d'un bloc toutes les étoiles créées par le code précédent ? Il suffit de les grouper, comme nous l'avons vu dans un chapitre précédent et de déclarer ce groupe "draggable". voici le code modifié en conséquence :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var groupe = new Kinetic.Group({
draggable: true
});
for(var i = 0; i < 10; ++i) {
var étoile = new Kinetic.Star({
x: Math.random() * scène.getWidth(),
y: Math.random() * scène.getHeight(),
numPoints: 5,
innerRadius: 40,
outerRadius: 80,
fill: '#823',
stroke: '#612',
opacity: 0.7,
strokeWidth: 10,
scale: (Math.random() * .8) + .2,
rotationDeg: Math.random() * 180
});
groupe.add(étoile);
}
calque.add(groupe);
scène.add(calque);
};
Cette fois c'est bien l'ensemble des étoiles qu'on peut déplacer en bloc.
Événements du glisser-déposer
Il y a 3 événements pour le glisser-déposer :
Événement
Description
dragstart
Se produit lorsqu'on commence à déplacer l'élément associé à l'événement.
dragmove
Se produit lorsqu'on déplace l'élément associé à l'événement.
dragend
Se produit lorsqu'on dépose l'élément associé à l'événement.
Voyons maintenant un exemple : on trace une ligne avec un disque à son extrémité. On veut pouvoir déplacer le disque avec la souris et que la ligne s'adapte automatiquement pour que son extrémité coïncide avec le disque :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var ligne = new Kinetic.Line({
points: [250, 150, 100, 100],
strokeWidth: 10,
lineCap: "round",
lineJoin: "round",
});
var disque = new Kinetic.Ellipse({
x: 100,
y: 100,
radius: 20,
fill: "red",
draggable: true
});
disque.on("dragmove", function() {
var pos = disque.getPosition();
ligne.setPoints([250, 150, pos.x, pos.y]);
calque.draw();
});
calque.add(ligne);
calque.add(disque);
scène.add(calque);
};
J'ai utilisé encore l'événement dragmove. On récupère la position du disque avec la méthode getPosition qui renvoie les coordonnées qu'il suffit ensuite d'appliquer à la ligne en définissant une nouvelle série de points de traçage.
Contraintes de déplacement
On veut parfois interdire à la forme déplacée de dépasser une certaine limite, ou alors de rester dans un espace particulier, en un mot contraindre le déplacement selon nos besoins. KineticJS dans ses précédentes versions ne prévoyait que deux possibilités : une contrainte linéaire horizontale ou verticale, ou une limite de déplacement. La version 4.02 a amélioré les choses, désormais on dispose d'une fonction pour écrire le code que l'on veut :) . La propriété à utiliser est dragBoundFunc. Elle prend comme valeur la fonction à utiliser pour effectuer la contrainte.
Contrainte linéaire
On peut facilement contraindre le déplacement sur un trajet linéaire, par exemple horizontal et vertical. Voici un exemple :
On peut aussi définir des limites de déplacement dans les 4 directions de l'espace. Voici un premier exemple avec un carré dont on limite les déplacements à l'intérieur de la scène pour éviter le débordement :
Je vous propose de réaliser un petit jeu qu'on va faire en deux étapes. Dans la première étape, on dessine un disque et on doit pouvoir le pousser avec le curseur de la souris de telle façon qu'il s'échappe à mesure qu'on le touche avec le curseur :
Il y a bien sûr des tas de façons de réaliser ça, mais vous allez être obligé de choisir le bon événement et de le coder correctement ainsi que tous les éléments à dessiner à l'écran. Vous devez effacer le texte dès que vous commencez le jeu. Il y a la propriété hide pour réaliser cela.
function getRandom() {
if(Math.random() > .5) return 1; else return -1;
}
function getPos(val, maxi) {
if(val < 20) return (val + 20);
if(val > maxi - 20) return (val - 20);
return val + 20 * getRandom();
}
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Disque = new Kinetic.Ellipse({
x: 250,
y: 150,
radius: 20,
fill: "red"
});
var Texte = new Kinetic.Text({
y: 10,
text: "Attrapez le disque !",
fontSize: 14,
fontFamily: "verdana",
fill: "grey",
lineHeight: 2,
width: 480,
align: "center"
});
Disque.on("mousemove", function() {
Texte.hide();
Disque.setX(getPos(Disque.getX(), 500));
Disque.setY(getPos(Disque.getY(), 300));
calque.draw();
});
calque.add(Disque);
calque.add(Texte);
scène.add(calque);
};
J'ai choisi l'événement mousemove qui me paraît adapté, je vous laisse analyser le code.
Passons à la deuxième étape de notre jeu. cette fois on va un peu améliorer en donnant un objectif, pousser le disque dans un carré :
Vous pouvez afficher un message de félicitation à la fin en changeant le texte et en le faisant réapparaître avec la méthode show. Vous aurez un peu plus de travail de codage, mais rien de bien difficile :p.
function getRandom() {
if(Math.random() > .5) return 1; else return -1;
}
function getPos(val, maxi) {
if(val < 20) return (val + 20);
if(val > maxi - 20) return (val - 20);
return val + 20 * getRandom();
}
function getPosBis(val, ref, maxi) {
if(val < 20) return (val + 20);
if(val > maxi - 20) return (val - 20);
if(ref - val > 0) return val - 20;
else return val + 20;
}
function testGain(x, y) {
return (Math.abs(x - 75) < 20) && (Math.abs(y - 75) < 20);
}
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Rectangle = new Kinetic.Rect({
x: 40,
y: 40,
width: 70,
height: 70,
stroke: "black",
strokeWidth: 4
});
var Disque = new Kinetic.Ellipse({
x: 250,
y: 150,
radius: 20,
fill: "red"
});
var Texte = new Kinetic.Text({
y: 10,
text: "Poussez le disque dans le carré !",
fontSize: 14,
fontFamily: "verdana",
fill: "grey",
lineHeight: 2,
width: 480,
align: "center"
});
Disque.on("mousemove", function() {
Texte.hide();
var mousePos = scène.getMousePosition();
if(Math.random() < .5) {
Disque.setX(getPos(Disque.getX(), 500));
Disque.setY(getPos(Disque.getY(), 300));
}
else {
Disque.setX(getPosBis(Disque.getX(), mousePos.x, 500));
Disque.setY(getPosBis(Disque.getY(), mousePos.y, 300));
}
if(testGain(Disque.getX(), Disque.getY())) {
Texte.setText("Bravo vous avez gagné !");
Texte.show();
Disque.setX(75);
Disque.setY(75);
Disque.off("mousemove");
}
calque.draw();
});
calque.add(Rectangle);
calque.add(Disque);
calque.add(Texte);
scène.add(calque);
};
Je n'ai pas particulièrement cherché à optimiser le code, le but n'est pas un exercice de style de Javascript mais de manipuler les objets de KineticJS. J'ai créé quelques sautes d'humeur aléatoires pour le déplacement du disque pour corser un peu le jeu.
Un tool-tip
Je vous propose maintenant de reprendre l'exemple où on détectait la position du curseur de la souris, mais cette fois en faisant apparaître les valeurs dans un tooltip :
Ce qui serait bien c'est que le tooltip n'apparaisse que lorsque le curseur est au-dessus de la scène. Il existe la propriété visible qui peut prend les valeurs true ou false pour gérer la visibilité d'un objet. On peut changer cette valeur ensuite avec les méthodes show et hide.
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var Rectangle = new Kinetic.Rect({
width: scène.getWidth(),
height: scène.getHeight(),
fill: "#ade",
});
var Tooltip = new Kinetic.Text({
text: "",
textFill: "yellow",
fontFamily: "Arial",
fontSize: 10,
padding: 4,
fill: "grey",
opacity: 0.9,
shadow: {
color: "grey",
blur: 14,
offset: [2, 2],
opacity: 0.7
},
visible: false
});
scène.on("mousemove", function() {
var mousePos = scène.getMousePosition();
var x = mousePos.x;
var y = mousePos.y;
var text = "X = " + x + " Y = " + y;
if(x > 400) x = 400;
if(y > 280) y = 280;
with(Tooltip) {
show();
setPosition(x, y);
setText(text);
}
calque.draw();
});
scène.on("mouseout", function() {
Tooltip.hide();
calque.draw();
});
calque.add(Rectangle);
calque.add(Tooltip);
scène.add(calque);
};
Les sprites
Les sprites
Les sprites sont des images d'une animation regroupées sur une seule image.
Le principe des sprites
Voici le sprite qui va nous servir pour les exemples (recueilli sur ce blog) :
Il est composé de 6 images, chacune étant une étape de l'animation. La lecture normale des images se fait en partant de la première en haut à gauche et en se déplaçant ensuite vers la droite et ainsi de suite :
Animation d'un sprite
C'est la classe Sprite qui nous permet d’animer des sprites. Son constructeur attend un positionnement, une image, des animations et éventuellement une vitesse. Voici le code pour mettre en mouvement la petite taupe :
Regardons la partie animation. On crée un objet animations qui contient une seule propriété (sortie) qui est en fait le nom d'une animation. Celle-ci est composée d'un tableau d'objets dont chacun est constitué de 4 propriétés :
Propriété
Fonction
x
Position sur l'axe X du point de référence (coin haut gauche)
y
Position sur l'axe Y du point de référence (coin haut gauche)
width
Largeur en pixels de l'image élémentaire
height
Hauteur en pixels de l'image élémentaire
Vous avez compris qu'il faut donc connaître l'organisation de l'image. En particulier il faut absolument que les images élémentaires soient uniformément réparties pour éviter des prises de tête sur la détermination des valeurs. En voici quelques-unes reportées sur le sprite :
C'est une méthode efficace, mais un peu laborieuse. On se rend compte de la répétition des largeurs et hauteurs et aussi de la répartition harmonieuse. On peut donc améliorer notre script en évitant toutes ces répétitions aussi pénibles qu'inesthétiques.
Où on améliore notre script
L'amélioration consiste essentiellement à optimiser la création du tableau des animations :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
// Génération des frames
function frames(base_x, base_y, offset_x, offset_y, nbr_x, nbr_y, width, height) {
var frames_tab = new Array();
for(var i = 0; i < nbr_x * nbr_y; ++i) {
frames_tab.push({
x: base_x + offset_x * ((i + nbr_x) % nbr_x),
y: base_y + offset_y * parseInt(i / nbr_x),
width: width,
height: height
});
}
return frames_tab;
}
// Animations
var animations = {
sortie: frames(10, 8, 165, 137, 3, 2, 158, 130)
};
// Image
var imageObj = new Image();
imageObj.onload = function() {
var animal = new Kinetic.Sprite({
x: 50,
y: 50,
image: imageObj,
animation: "sortie",
animations: animations,
frameRate: 4
});
calque.add(animal);
scène.add(calque);
animal.start();
};
imageObj.src = "images/gopher.jpg";
};
Une fonction frames est chargée de faire le calcul du positionnement des images élémentaires. Elle attend 6 paramètres dont voici une visualisation :
C'est déjà moins prise de tête pour constituer une animation :p .
Plusieurs animations
Jusque-là, nous nous sommes contentés de créer une seule animation avec toutes les images prévues dans l'ordre. On pourrait aussi avoir envie de créer des animations avec certaines images à chaque fois, et pas forcément dans l'ordre. Notre code alors ne convient plus et il nous faut le rendre plus générique. Voici un exemple avec deux animations déclenchées par des boutons :
Le code devient un peu plus lourd. Remarquez déjà les deux méthodes start et stop pour démarrer et arrêter l'animation. Remarquez également l'événement afterFrame qui permet d’exécuter du code après une certaine image. J'ai utilisé également la méthode setAnimation qui affecte l'animation en cours avec son nom.
Mais c'est surtout la fonction frames qui a été chamboulée. Maintenant elle attend non seulement les paramètres de positionnement et dimension des images, mais également un tableau des index des images composant l'animation dans l'ordre. Du coup l'écriture du code des animations devient un jeu d'enfant :
On se contente d'envoyer un tableau avec les index des images que l'on veut :) .
TP
Maintenant vous êtes prêts à faire un petit TP dans la lignée des scripts précédents. Cette fois on désire 3 animations : une pour la sortie de la taupe, une pour la faire regarder autour d'elle, et enfin une dernière pour qu'elle retourne dans son trou. On utilise trois boutons qui ne doivent apparaître que lorsqu'ils sont utiles. Voici l'exemple traité qui sera beaucoup mieux que toutes mes explications :
Au départ la taupe peut seulement sortir, donc seulement le bouton "Sortie" doit apparaître. Lorsqu'elle est sortie, elle peut alors soit surveiller, soit rentrer, donc les deux autres boutons doivent être affichés. Lorsqu'elle surveille, elle ne peut plus que rentrer... Au niveau du code il vous faut bien gérer l'événement afterFrame pour éviter les surprises.
Nous allons pour terminer aborder les animations libres. C'est sans doute l'aspect le plus créatif de cette librairie, mais en même temps elle va peu nous aider, parce qu'il faut écrire le code pour l'animation, mais elle nous offre une infrastructure performante.
Un exemple simple
Voici un exemple élémentaire avec une balle qui se déplace dans un cadre :
window.onload = function() {
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var cadre = new Kinetic.Rect({
width: 500,
height: 300,
stroke:"black",
strokeWidth:2
});
var balle = new Kinetic.Circle({
x: 200,
y: 100,
radius: 20,
fill: "#6b9"
});
var deltax = -1;
var deltay = 2;
var rayon=balle.getRadius()
var anim = new Kinetic.Animation(function(frame) {
if (balle.getX()-rayon < 0 || balle.getX()+rayon > scène.getWidth()){
deltax=-deltax;
}
if (balle.getY()-rayon < 0 || balle.getY()+rayon > scène.getHeight()){
deltay=-deltay;
}
document.getElementById("texte").innerHTML = "x : " + deltax+" y : "+deltay;
balle.move(deltax, deltay);
}, calque);
anim.start();
calque.add(cadre);
calque.add(balle);
scène.add(calque);
};
Dans cet exemple on déplace une balle le long d'une ellipse :
window.onload = function() {
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
var calque = new Kinetic.Layer();
var fond = new Kinetic.Layer();
var a = 150;
var b = 80;
var centre_x = scène.getWidth() / 2;
var centre_y = scène.getHeight() / 2;
var balle = new Kinetic.Ellipse({
radius: 10,
fill: "red"
});
var ellipse = new Kinetic.Ellipse({
x: centre_x,
y: centre_y,
radius: [a, b],
stroke: "black",
strokeWidth: .7
});
var anim = new Kinetic.Animation(function(frame) {
var x = a * Math.cos(frame.time / 2000);
var y = b * Math.sin (frame.time / 2000);
balle.setX(centre_x + x);
balle.setY(centre_y + y);
}, calque);
anim.start();
calque.add(balle);
fond.add(ellipse);
scène.add(fond);
scène.add(calque);
};
Nous allons nous intéresser à la vitesse de déplacement de cette balle. Vous remarquez dans le code l'utilisation de la propriété time du paramètre frame. Celui-ci nous donne le temps écoulé depuis le début de l'animation en millisecondes. Il existe une deuxième propriété qui nous donne le temps écoulé depuis le dernier rafraîchissement. Voici ces deux paramètres :
Paramètre
Valeur
time
Temps écoulé depuis le début de l'animation en millisecondes
timeDiff
Temps écoulé depuis le dernier rafraîchissement en millisecondes
Ce sont ces propriétés qui vont nous permettre de gérer efficacement le temps. Voyons cela avec notre balle, voici le code de calcul de la position :
var x = a * Math.cos(frame.time / 2000);
var y = b * Math.sin (frame.time / 2000);
balle.setX(centre_x + x);
balle.setY(centre_y + y);
On sait que la fonction cosinus de Javascript attend une valeur en radians. Autrement dit un tour complet est effectué quand on passe d'une valeur de 0 à 2 * PI, autrement dit de 0 à 6,28. Comme je divise le temps par 2000 j'ai une progression de 0,5 à chaque seconde, il me faut donc 6,28 / 0,5 = 12,56 secondes pour effectuer un tour complet. A condition évidemment que le rafraîchissement s'effectue à une vitesse suffisante :) . On va d'ailleurs vérifier cela en ajoutant un contrôle du temps :
var anim = new Kinetic.Animation(function(frame) {
document.getElementById("texte").innerHTML = "Temps écoulé en secondes : " + frame.time / 1000 + " fps : " + parseInt(1000 / frame.timeDiff);
var x = a * Math.cos(frame.time / 2000);
var y = b * Math.sin (frame.time / 2000);
balle.setX(centre_x + x);
balle.setY(centre_y + y);
}, calque);
C'est une forme allongée qui va en rétrécissant en formant une queue qui suit une ellipse. Pour constituer ce genre d'effet le plus simple est d'empiler des disque colorés. A vous de jouer ;) .
window.onload = function() {
// Scène
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
// Constitution du fond dégradé
var fond = new Kinetic.Layer();
var rectangle = new Kinetic.Rect({
width: 500,
height: 300,
fillLinearGradientStartPoint:[scène.getWidth() / 2,0],
fillLinearGradientEndPoint:[scène.getWidth() / 2,scène.getHeight()],
fillLinearGradientColorStops:[0, "#27f", 1, "#016"]
});
fond.add(rectangle);
// Constitution des balles
var calque = new Kinetic.Layer();
var a = 150;
var b = 80;
var centre_x = scène.getWidth() / 2;
var centre_y = scène.getHeight() / 2;
var ball_number = 40;
var offset = .035;
var red = 140;
var green = 50;
var blue = 70;
for(var i = 1; i <= ball_number; ++i) {
var balle = new Kinetic.Ellipse({
radius: 10,
fill: "rgb(" + (red + 3 * i) + "," + (green + 3 * i) + "," + (blue + i) + ")",
opacity: .8 - (i / ball_number) * .8,
scale: 1 - i / ball_number
});
calque.add(balle);
}
scène.add(fond);
scène.add(calque);
// Animation
var anim = new Kinetic.Animation(function(frame) {
var balles = calque.getChildren();
var angle = frame.time / 500;
for(var i = 0; i < balles.length; ++i) {
var balle = balles[i];
var angle_ball = angle - i * offset;
var x = a * Math.cos(angle_ball);
var y = b * Math.sin (angle_ball);
var posX = centre_x + x;
var posY = centre_y + y;
balle.setX(posX);
balle.setY(posY);
}
}, calque);
anim.start();
};
Comme je vous sens encore en forme, on va améliorer l'effet :
Cette fois la forme est plus volumineuse, avec un dégradé, un halo et elle suit un huit (un peu de maths en passant). J'ai aussi ajouté le contrôle du rafraîchissement pour juger les performances de la librairie.
window.onload = function() {
// Scène
var scène = new Kinetic.Stage({
container: "kinetic",
width: 500,
height: 300
});
// Constitution du fond dégradé
// Constitution du fond dégradé
var fond = new Kinetic.Layer();
var calque = new Kinetic.Layer();
var rectangle = new Kinetic.Rect({
width: 500,
height: 300,
fillLinearGradientStartPoint:[scène.getWidth() / 2,0],
fillLinearGradientEndPoint:[scène.getWidth() / 2,scène.getHeight()],
fillLinearGradientColorStops:[0, "#27f", 1, "#016"]
});
fond.add(rectangle);
// Constitution des balles
var calque = new Kinetic.Layer();
var a = 150;
var b = 80;
var centre_x = scène.getWidth() / 2;
var centre_y = scène.getHeight() / 2;
var ball_number = 40;
var offset = .03;
// Halo
for(var i = 1; i <= ball_number; ++i) {
var balle = new Kinetic.Ellipse({
radius: 20,
fill: "yellow",
opacity: .04,
scale: (1 - i / ball_number) + .4
});
calque.add(balle);
}
// Corps
for(var i = 1; i <= ball_number; ++i) {
var balle = new Kinetic.Ellipse({
radius: 20,
fillRadialGradientStartPoint:[0,0],
fillRadialGradientEndPoint:[0,0],
fillRadialGradientStartRadius:3,
fillRadialGradientEndRadius:20,
fillRadialGradientColorStops:[.6, "red", .8, "#cc2"],
opacity: .8 - (i / ball_number) * .8,
scale: 1 - i / ball_number
});
calque.add(balle);
}
scène.add(fond);
scène.add(calque);
// Animation
var k = 2;
var anim = new Kinetic.Animation(function(frame) {
var balles = calque.getChildren();
var angle = frame.time / 500;
for(var i = 0; i < balles.length / 2; ++i) {
var balle1 = balles[i];
var balle2 = balles[i + balles.length / 2];
var angle_ball = angle - i * offset;
var x = a * Math.cos(angle_ball);
var y = b * Math.sin (k * angle_ball);
var posX = centre_x + x;
var posY = centre_y + y;
balle1.setX(posX);
balle1.setY(posY);
balle2.setX(posX);
balle2.setY(posY);
}
}, calque);
anim.start();
};
Nous voici arrivés au bout de ce cours. Je suis loin d'avoir été exhaustif, mais vous avez déjà les bases pour créer de belles choses.