Partage
  • Partager sur Facebook
  • Partager sur Twitter

Commenter le code d'un débutant.

14 janvier 2017 à 16:30:27

Je suis débutant en JS, j'ai lu un paquet de cours, du CSS, HTML ...

Je me suis lancé un petit défi. Faire mon premier programme JS en utilisant au mieux les objets.

Le programme fonctionne mais je pense pas que les objets que j'ai créer sois les plus efficaces.

Je vous mets donc tout mon code a disposition, et si vous avez des remarques à me faire (et il y en aura surement) ça me permettra d’être meilleur pour le prochain coup.

Le but du programmeest de travailler sur les images. Accéder au pixel, faire une petite sauce avec et afficher le résultat dans un autre canvas.

En gros le programme ce déroule comme ca :

-Je crée les 2 canvas et on les met au dim de l'image.

-Je fait un tableau a 2 dimension contenant les pixels de l'image.(leur couleur en fait)

-Je tire au hazard une position dans l'image et je dessine dans le second canvas un petit cercle au même endroit a la couleur du pixel choisi.

ca tourne en boucle et ça donne une image assez fun en fonction du canal alpha et du rayon du cercle choisi.

Voila donc le code :

le fichier style.css

canvas
{
	border: 1px solid black;
	
}
#conteneur
{
	border : 1px solid black; 
	display: flex;
	justify-content : center;
}

#photo
{
	border : 1px solid black;
}

le fichier index.html

<!DOCTYPE html>
<html>
	
	<head>
		<meta charset="utf-8">
		<title>jeu photo</title>
		<link rel="stylesheet" type="text/css" href="css/style.css" />

		<script type="text/javascript" src="js/classes/Rgba.js"></script>
		<script type="text/javascript" src="js/classes/PixelMap.js"></script>
		<script type="text/javascript" src ="js/classes/Blob.js"></script>
		
		<script type="text/javascript" src="js/index.js"></script> 

	</head>

	<body>
		<div id = conteneur>
			<div><canvas id="canvas1"> votre nav a pas html5</canvas></div>   
			<div><canvas id="canvas2"> votre nav a pas html5</canvas></div>
		</div>
	</body>

</html>

le fichier index.js

window.onload = function() 
{
  var image = new Image();

  image.onload = function()
  {
    var canvas1 =  getCanvas("canvas1");
    var canvas2 =  getCanvas("canvas2");
    var ctx1 = getContext(canvas1);
    var ctx2 = getContext(canvas2);
    setCanvasDim(ctx1, image.width, image.height);
    setCanvasDim(ctx2, image.width, image.height); 

    ctx1.drawImage(image,0,0);

    var pixelMap = new PixelMap(getImageData(ctx1));
    pixelMap.remplirGrille();

    var blob = new Blob(ctx2);

    stop = setInterval(function() 
    {
      blob.move(pixelMap);
      blob.show();  
    }, 0);

  }
  image.src = "image/chat.jpg";

}



function setCanvasDim(context, width, height)
{
  context.canvas.width = width;
  context.canvas.height = height;
}

function getCanvas(id)
{
  var canvas =  document.getElementById(id);
  if(!canvas)
  {
    alert("Impossible de récupérer le canvas " + id);
    return;
  }
  return canvas;
}

function getContext(canvas)
{
  var context = canvas.getContext('2d');
  if(!context)
  {
    alert("Impossible de récupérer le context du canvas");
    return;
  }
  return context;
}


function getImageData(context)
{
    var imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
    if(!imageData)
    {
        alert("Impossible de récupérer le ImageData");
        return;
    }
    return imageData;
}






window.onkeydown = function(event) 
{
    var e = event || window.event;
    var key = e.which || e.keyCode;

    switch(key)
    {
        case 38 :
            
            break;
        case 40 :
            clearInterval(stop);
            break;
    }
}

 

La classe Blob.js

function Blob(ctx)
{
	this.x = 0;
	this.y = 0;
	this.r = 5;
	this.color="";

	this.move = function(pixelMap)
	{
		this.x = getRandom (0, ctx.canvas.width-1);
		this.y = getRandom (0, ctx.canvas.height-1);
		this.color = rgbaToString(pixelMap.getPixelImage(this.x,this.y));
	}

	this.show = function()
	{
		ctx.fillStyle = this.color;
		ctx.beginPath();
		ctx.arc(this.x, this.y, this.r, 0, Math.PI*2);
		ctx.fill();
		ctx.closePath();
	}
}


function getRandom(min,max)
{
	return Math.floor (( Math.random() * max - min + 1)+min);
}

le classe PixelMap.js

function PixelMap(imageData)
{
	this.width = imageData.width; // en pixel
	this.height = imageData.height; // en pixel

	this.grille = new Array(this.width);

	for ( var x = 0; x < this.width; x++)
	{
		this.grille[x] = new Array(this.height);
	}

	this.remplirGrille = function()
	{
		for (var y = 0; y < this.height; y++) 
		{
			for (var x = 0; x < this.width; x++) 
			{
				this.grille[x][y] = this.getPixelImage(x,y);
			}
			
		}
	}

	this.getPixelImage = function(a,b)
	{
		var couleur = new Rgba();
		var index = 4*(a + b*this.width);

		couleur["r"] = imageData.data[index];
		couleur["g"] = imageData.data[index+1];
		couleur["b"] = imageData.data[index+2];
		couleur["a"] = imageData.data[index+3];

		return couleur;
	}
}

La classe Rgba.js

function Rgba()
{
	this.r=0;
	this.g=0;
	this.b=0;
	this.a=0;
}

function rgbaToString(Rgba)
{
	//var text = "rgba(" + Rgba.r + "," + Rgba.g +","+ Rgba.b +"," + Rgba.a+")"; 
	var text = "rgba(" + Rgba.r + "," + Rgba.g +","+ Rgba.b +"," + 1+")"; 
	//var text = "rgba(" + Rgba.r + "," + 100 +","+ Rgba.b +"," + 1+")"; 
	return text;
}

Pour ranger tout ca j'ai :

dossier principal
/css
//style.css
/image
//votre image
/js
//classes
///Rgba.js
///Blob.js
///PixelMap.js
/index(fichier html)








-
Edité par htmlOne 14 janvier 2017 à 16:38:47

  • Partager sur Facebook
  • Partager sur Twitter
18 janvier 2017 à 14:15:41

Salut,

alors voici les "conseils" que je peux te donner :

Utilises addEventListener(), tu éviteras certains problèmes de "collision" le jour où tu utilises plusieurs script développés indépendamment.

Attention au classe JS existantes, car Blob existe déjà :s

Utilises les prototypes, c'est assez complexe au début, mais une fois que tu as compris pourquoi et comment, c'est assez simple. Et tu peux même maintenant utiliser la syntaxe à l'objet avec class, super etc ... ( si tu ne prends pas en compte les anciens navigateurs ).

Après le reste à l'air correcte, juste quelques questions :

Pourquoi ne pas faire un objet global ? genre APP avec tes fonctions globals, tes canvas en mémoire, etc ... sans forcément faire un constructeur, juste un simple objet. Tu pourras ainsi faire changer l'image par l'utilisateur ou générer un autre résultat en fonction d'un formulaire par exemple.

Pourquoi ne pas utiliser setTimeout au lieu de setInterval ? voir même requestAnimationFrame ? parce que setInterval n'est pas top : https://openclassrooms.com/courses/dynamisez-vos-sites-web-avec-javascript/la-gestion-du-temps-3#/id/r-1926631

Pourquoi faire qu'un seul Blob et pas plusieurs ? parce que sinon il n'y a aucun intérêt de faire un constructeur, et tu pourras faire d'autres choses intéressantes plus tard, comme animer ton image en jouant sur les rayons de ceux-ci par exemple.

  • Partager sur Facebook
  • Partager sur Twitter
18 janvier 2017 à 22:05:02

Salut ! je te remercie d'avoir regardé mon travail.

1-Utilises addEventListener(), tu éviteras certains problèmes de "collision"

Il va falloir que je me renseigne a quoi ça sert je n'ai jamais utilisé.

2-Blob existe déjà

Je ne savais pas ! Comment peut on connaitre les objet qui existe ?

-3Utilises les prototypes

J'ai commencé a lire des choses la dessus. Mais je dois t'avouer que je n'ai pas encore bien compris les avantages que ça peux apporter.

-4Et tu peux même maintenant utiliser la syntaxe à l'objet avec class, super etc ...

Ca j'avais vu aussi mais il était écris que ce n'était qu'en fait une sorte d'illusion et que ça n'existait pas vraiment en JS. Puisque c'est la première fois que j'utilise un langage objet, je me suis dit autant essayer de coller au plus près du fonctionnement de ce langage. Du coup j'y ai pas prêté trop attention.

-5 Pourquoi ne pas faire un objet global ? genre APP

C'est quoi APP ?

-6 Pourquoi ne pas utiliser setTimeout au lieu de setInterval ? voir même requestAnimationFrame

j'ai récupérer la fonction d'un cours sur comment faire un rpg avec canvas. Je n'avais simplement jamais entendu parler des autres.

En fait j'ai suivie les cours

la balise <canvas> avec JS

créer un mini rpg en js avec canvas

tout sur js

apprenez a créer votre site web avec html5 et css3

apprenez a coder avec js

comprendre de web

Mais pourtant il y a plein de chose dont tu me parles et que je ne connais pas. Tu sais comment je pourrais trouver d'autre cours ?





  • Partager sur Facebook
  • Partager sur Twitter
18 janvier 2017 à 23:56:19

Hello,

.addEventListener()permet d'écouter un événement et s'inscrit dans l'API DOM-2. Par exemple, plutôt que de faire window.onload = function() { ... } tu peux écrire window.addEventListener('load', function() { ... });

Comme l'a dit Wheel's, l'avantage de cette méthode est qu'elle permet d'attacher plusieurs évènements du même type sur un même objet (et donc n'entre pas en conflit avec d'autres librairies/frameworks)

Comprendre :

window.onclick = function() { alert('click 1'); }
window.onclick = function() { alert('click 2'); }
Problème : quand on cliquera dans la fenêtre, seul l'alerte click 2 sera affichée car cette 2ème fonction stockée dans la propriété .onclick a écrasé la 1ère valeur. Avec .addEventListener(), on n'a plus ce problème :
window.addEventListener('click', function() { alert('click 1')};
window.addEventListener('click', function() { alert('click 2')};
Les 2 fonctions s'enregistrent bien sur le même élément et se déclencheront successivement dans l'ordre de leur enregistrement, affichant ainsi : click 1 et click 2

Concernant les prototypes, en fait c'est pas hyper compliqué. Plutôt que de faire ça :

// Mauvais exemple ! Ne pas faire ceci ...
function PixelMap() {
  this.width = imageData.width; // en pixel
  this.height = imageData.height; // en pixel

  this.remplirGrille = function() { ... };
}

... il est conseillé de faire ceci :

// Bon exemple ! :-]
function PixelMap() {
  this.width = imageData.width; // en pixel
  this.height = imageData.height; // en pixel
}
PixelMap.prototype.remplirGrille = function() { ... };

Soit, déplacer tes méthodes d'objet dans le prototype de la fonction constructible.

On le fait surtout pour des raisons de performances. Saches que si tu créé 30 instances de ton objet PixelMap avec le code du mauvais exemple, la méthode .remplirGrille() (dont le travail est commun pour toutes les instances d'objets) sera copiée en mémoire dans l'instance créée. Donc chacune de tes instances contiendra un duplicata de la méthode .remplirGrille(), ce qui n'est pas optimal.

Chaque fonction est équipée d'un objet prototype, qui permet notamment de stocker ces méthodes. Si une instance appelle une méthode .remplirGrille(), JavaScript va d'abord vérifier si cette méthode est présente dans les ownProperties de l'objets, et si ce n'est pas le cas, va "remonter" voir dans le prototype pour essayer de la trouver. On appelle cela la chaîne de lookup.


-4Et tu peux même maintenant utiliser la syntaxe à l'objet avec class, super etc ...

Ca j'avais vu aussi mais il était écris que ce n'était qu'en fait une sorte d'illusion et que ça n'existait pas vraiment en JS. Puisque c'est la première fois que j'utilise un langage objet, je me suis dit autant essayer de coller au plus près du fonctionnement de ce langage. Du coup j'y ai pas prêté trop attention.

Si. Les navigateurs récents supportent plutôt bien ces nouveautés. Cependant si tu veux t'assurer une rétro-compatibilité, je te conseille grandement de jeter un œil au projet Babel ;)

Ne t'en fais pas, toutes ces choses là s’acquièrent avec le temps et la pratique ;) Je te conseille la série de vidéos https://javascript30.com/ : 30 jours, 30 challenges, 100% pur JS moderne, gratuit !

-
Edité par ninjavascript 19 janvier 2017 à 0:24:28

  • Partager sur Facebook
  • Partager sur Twitter
22 janvier 2017 à 23:38:18

Après quelque jours de réflexion, je pense que je vais essayer de refaire le projet entièrement en prenant en compte vos conseils.

Par contre, vos réponses m'ont apporté d'autre question.

Sachant qu'il y a plusieurs moyen de créer un objet en JS,(si je ne me suis pas trompé il y a déjà c'est 2 façons:

function Fruit()
{
	this.nom;
	this.couleur;
	this.origine;
}

var fruit{
	nom : "orange",
	couleur : "orange",
	origine : "france"
}

Laquelle est la "meilleur" ? Y a t'il une différence entre les 2 moyens de faire ?

Avec "function fruit()", il me semble plus simple d'initialiser les propriétés d'un nouvelle objet de type fruit en utilisant les paramètres de la fonction. avec "var fruit{}" créer tout un tableau de fruit différents me parait plus compliquer. (Voir créer une methode fruit.init()).

La deuxième grande question est l'organisation des différents objet du projet. Ce petit paragraphe reste très obscure a mes yeux.

Wheel's a écrit:

Pourquoi ne pas faire un objet global ? genre APP avec tes fonctions globals, tes canvas en mémoire, etc ... sans forcément faire un constructeur, juste un simple objet. Tu pourras ainsi faire changer l'image par l'utilisateur ou générer un autre résultat en fonction d'un formulaire par exemple.





  • Partager sur Facebook
  • Partager sur Twitter
23 janvier 2017 à 14:26:52

Re,

désolé de ne pas avoir pus te répondre plutôt.

Alors voici une listes des Objets JS : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux.
Je te vois venir, tu vas me dire que Blob n'est pas dedans ^^, je te laisse donc ensuite regarder ceci : https://developer.mozilla.org/en-US/docs/Web/API.

Ca tombe bien que tu poses cette question, je vais pouvoir t'expliquer mon point de vue et l'objet global APP ...

Comme tu le dis, il y a deux moyen de créer un objet, soit via un constructeur, soit de manière littéral.

Le seul intérêt de passer par un constructeur est de pouvoir créer plusieurs instances basé sur le prototype de celui-ci, donc si tu as un objet que tu peux réutiliser, utilises un constructeur, sinon fais le de manière littéral ( enfin c'est comme ça que je procède ).

Donc même si j'ai besoin d'un objet avec des méthodes, des propriétés mais que je n'utilise qu'une seul fois ( un singleton en quelque sorte ), je fais un objet littéral, sinon je définie un constructeur et son prototype.

On en arrive du coup à APP, l'idée est d'avoir un objet global, donc accessible partout, avec des méthodes et propriétés utiles pour le fonctionnement de mon projet, ou de mon application ( d'ou le nom APP :p ), mais qui est unique, donc littéral.

En reprenant le script que tu as donné au début, sans faire de grosse modification sur ton script, on obtient :

var APP = {
    canvas1: null,
    canvas2: null,
    ctx1: null,
    ctx2: null,
    interval: null,
    
    init: function(){
        this.canvas1 = this.getCanvas('canvas1');
        this.canvas2 = this.getCanvas('canvas2');
        this.ctx1 = this.getContext(canvas1);
        this.ctx2 = this.getContext(canvas2);
    },
    getCanvas: function(id){
        var canvas = document.getElementById(id);
        if (!canvas){
            alert('Impossible de récupérer le canvas ' + id);
            return;
        }
        return canvas;
    },
    setCanvasDim: function(canvas, width, height){
        canvas.width = width;
        canvas.height = height;
    },
    getContext: function(canvas){
        var context = canvas.getContext('2d');
        if (!context){
            alert('Impossible de récupérer le context du canvas');
            return;
        }
        return context;
    },
    getImageData: function(context){
        var imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
        if (!imageData){
            alert('Impossible de récupérer le ImageData');
            return;
        }
        return imageData;
    },
    drawImage: function(image){
        this.setCanvasDim(this.canvas1, image.width, image.height);
        this.setCanvasDim(this.canvas2, image.width, image.height);
        this.ctx1.drawImage(image, 0, 0);
        
        var pixelMap = new PixelMap( this.getImageData(ctx1) );
        pixelMap.remplirGrille();
        var blob = new Blob(this.ctx2);
        this.interval = setInterval(
            function (){
                blob.move(pixelMap);
                blob.show();
            },
            0
        );
    }
};

window.addEventListener('load', function(){
    APP.init();
    var image = new Image();
    image.addEventListener('load', function(){
        APP.drawImage(image);
    }, false);
    image.src = 'image/chat.jpg';
}, false);

window.addEventListener('keydown', function(event){
    var e = event || window.event;
    var key = e.which || e.keyCode;
    switch(key){
        case 40 :
            clearInterval(APP.interval);
            break;
    }
}, false);

 Il ne te reste plus qu'a segmenter ton script en plusieurs partie ou étape, que tu mets dans ton objet global, pour ensuite avoir une plus grande flexibilité et permettre d’interagir avec l'utilisateurs.

-
Edité par Wheel's 23 janvier 2017 à 14:36:32

  • Partager sur Facebook
  • Partager sur Twitter
23 janvier 2017 à 17:43:21

Je tombe encore sur un point qui n'était pas clairement énoncé dans les cours, Ou alors je suis complètement a la ramasse.

j'ai essayer de "mixer" 2 conseils.

faire un objet APP déclaré de manière littéral et mettre les méthodes de cet objet dans le prototype de APP. c'est a dire faire ça :

var APP = {
    canvas1: null,
    canvas2: null,
    ctx1: null,
    ctx2: null,
    interval: null  
};




APP.prototype.init = function(){
    this.canvas1 = this.getCanvas('canvas1');
    this.canvas2 = this.getCanvas('canvas2');
    this.ctx1 = this.getContext(canvas1);
    this.ctx2 = this.getContext(canvas2);
}

APP.prototype.getCanvas = function(id){
    var canvas = document.getElementById(id);
    if (!canvas){
        alert('Impossible de récupérer le canvas ' + id);
        return;
    }
    return canvas;
}

ça ne marche pas du tout. :lol: C'est normal ou j'ai mal écrit quelque chose ?

  • Partager sur Facebook
  • Partager sur Twitter
24 janvier 2017 à 7:05:45

C'est normal car ça na aucun sens ^^.

Je te laisse lire ceci : https://developer.mozilla.org/fr/docs/Web/JavaScript/H%C3%A9ritage_et_cha%C3%AEne_de_prototypes

Comme je t'ai dit avant : si j'ai besoin d'un objet [...] que je n'utilise qu'une seul fois, je fais un objet littéral, sinon je définie un constructeur et son prototype.

En gros, lorsque tu fais un constructeur, tu dois faire son prototype, car c'est sur celui-ci que vont se baser les instances créée par se constructeur.

Donc si tu fais un constructeur, alors tu dois définir son prototype.

Lorsque tu créer un objet littéral, qui ça revient à utiliser le constructeur Object, c'est seulement pour une utilisation unique donc aucun intérêt de touché au prototype car lui seul connait cette méthode ou propriété.

Et même si tu touches au prototype de Object, toutes et je dit bien toutes tes variables auront accès à ce que tu ajoutes ! ( ce n'est pas trop conseiller hormis pour faire du polyfill )

Et oui car en JS tout est issue de Object donc n'importe que variable en aura accès via la chaine de prototypage.

-
Edité par Wheel's 24 janvier 2017 à 7:06:17

  • Partager sur Facebook
  • Partager sur Twitter
25 janvier 2017 à 19:18:42

Je commence à y avoir plus clair dans tout ça. Je tiens vraiment à te remercier pour tes réponses.

Je continue a explorer le sujet et je suis tombé sur un "truc" intéressant.

voila les 2 bouts de code dont il s'agit.

 update : function(){

        if (this.stop){
            return;}
        blob.move(pixelMap);
        blob.show();
        this.update();

    }
 update : function(){
        blob.move(pixelMap);
        blob.show();
        APP.timeoutID = setTimeout(APP.update);
    }


dans le premier cas je fait une récursivité et je "peux" arrêter la boucle grâce a un évènement clavier qui passerait stop a true. (dans la réalité le programme bloque trop vite).

Dans le deuxième cas j'utilise setTimeout ,j’arrête la boucle avec un cleanTimeout grâce a un évènement clavier aussi.

Ce qui est intéressant c'est la différence de vitesse ENORME qu'il y a entre les 2 techniques. Avec la première, j'arrive instantanément à une image mais le programme s’arrête. avec un message "too much recursion".

Avec la méthode 2 il me faut bien 10 secondes pour avec le même nombre de point mais le programme peut continuer a l'infini.

Du coup je me demandais si il n'y avait pas moyen de booster la méthode Timeout ou quelque chose dans le genre histoire d’accélérer les choses.

J'aimerais bien voir des images dont le résultat est un très très grand nombre de point sans avoir a attendre trop.

-
Edité par htmlOne 25 janvier 2017 à 19:20:33

  • Partager sur Facebook
  • Partager sur Twitter
26 janvier 2017 à 8:35:13

Re

Le premier code ne marche pas, car il soulève une erreur.

Comme tu fais update() de manière récursive, sans passer par un délai d'attente, ton code boucle jusqu'à se que le navigateur se rends compte que ça n'en finira jamais et lève une erreur, puis seulement après t'affiches à l'écran ton résultat. Donc tu as l'impression que c'est plus rapide, mais ça doit l'être assez peux.

Tu dois donc passer par une fonction qui rappelle update() mais décaler dans le temps, afin de ne pas bloquer ton script.

Alors perso. quand je fais quelque chose qui nécessite une boucle dans le temps, de manière défini, j'utilise setTimeout de la même façon que toi ( animation d'une <div> comme un slide par exemple ).

Quand je fais de l'animation indéfinie, j'utilise requestAnimationFrame ( pour une jeu ou animation ) pour avoir la fluidité classique de 60fps.

Donc tu as la bonne solution, c'est juste que ton script demande du temps pour s’exécuter car tu souhaites un nombre conséquent de point.

Sinon tu définis des le départ le nombre de points et tu fais une boucle classique for, après il faut faire des tests pour si vraiment tu es gagnant pour un même nombre de point.

-
Edité par Wheel's 26 janvier 2017 à 8:56:11

  • Partager sur Facebook
  • Partager sur Twitter
26 janvier 2017 à 17:46:43

Voila une solution qui marche. C'est peu être un peu de la bidouille mais ça va beaucoup plus vite qu'avec un timeout simple.

donc ce que tu as dis ici n'est pas tout a fait correct.

Wheel's a écrit:

 ton code boucle jusqu'à se que le navigateur se rends compte que ça n'en finira jamais et lève une erreur, puis seulement après t'affiches à l'écran ton résultat. Donc tu as l'impression que c'est plus rapide, mais ça doit l'être assez peux.

 update : function(){
        for(var i = 0; i<5000; i++){
            blob.move(pixelMap);
            blob.show();
        }
        APP.timeoutID = setTimeout(APP.update);
    }
Par contre on met un trop grand nombre pour la boucle for ça fait des truc pas terrible. :lol:

-
Edité par htmlOne 26 janvier 2017 à 17:47:15

  • Partager sur Facebook
  • Partager sur Twitter
27 janvier 2017 à 1:29:11

Forcément, tu ne précises pas de delay à setTimeout(), du coup cela veut dire que tu exécutes APP.update quasiment instantanément.

En admettant que quasiment instantanément signifie qqch comme 50 fois par secondes, cela voudrait dire que tu demandes à ton CPU toutes les secondes d'effectuer 50 × 5000 tours de boucle, dans laquelle tu fais une opération potentiellement lourde (selon ce que font tes fonctions blob.show() et blob.move()).

En gros, 5 secondes de ton programme => 5 × 50 × 5000 = 1 250 000 tours de boucle ... Normal que ça puisse ramer un peu si tu augmentes encore le nb de tours ;-)

-
Edité par ninjavascript 27 janvier 2017 à 1:31:00

  • Partager sur Facebook
  • Partager sur Twitter