Beau code est une joie d'écrire, mais il est difficile de partager cette joie avec d'autres programmeurs, sans parler des non-programmeurs. Dans mon temps libre entre mon travail de jour et mon temps en famille, j'ai joué avec l'idée d'un poème de programmation utilisant l'élément canvas pour dessiner dans le navigateur. Il existe une multitude de termes décrivant des expériences visuelles sur ordinateur, telles que la dev art, le croquis de code, la démo et l' art interactif, mais j'ai finalement opté pour un poème de programmation pour décrire ce processus. L'idée derrière un poème est un morceau de prose poli, facile à partager, concis et esthétique. Ce n'est pas une idée à moitié terminée dans un carnet de croquis, mais une pièce cohérente présentée au spectateur pour son plaisir. Un poème n'est pas un outil, mais existe pour évoquer une émotion.
Pour mon plaisir, j'ai lu des livres sur les mathématiques, l'informatique, la physique et la biologie. J'ai appris très rapidement que lorsque je me promène sur une idée, cela ennuie assez rapidement les gens. Visuellement, je peux prendre certaines de ces idées que je trouve fascinantes et donner rapidement un sentiment d'émerveillement à toutes les personnes, même si elles ne comprennent pas la théorie qui sous-tend le code et les concepts. Vous n'avez pas besoin de vous soucier de la philosophie ou des mathématiques pour écrire un poème de programmation, juste un désir de voir quelque chose de vivant et de respirer à l'écran.
Le code et les exemples que j'ai rassemblés ci-dessous vous aideront à comprendre comment réaliser ce processus rapide et hautement satisfaisant. Si vous souhaitez suivre avec le code que vous pouvez Téléchargez les fichiers sources ici.
L'astuce principale lors de la création d'un poème est de le garder léger et simple. Ne passez pas trois mois à construire une démo vraiment cool. Au lieu de cela, créez 10 poèmes qui développent une idée. Ecrivez un code expérimental passionnant et n'ayez pas peur d'échouer.
Pour un aperçu rapide, le canevas est essentiellement un élément d'image bitmap 2D qui réside dans le DOM pouvant être dessiné. Le dessin peut être fait en utilisant un contexte 2d ou un contexte WebGL. Le contexte est l'objet JavaScript que vous utilisez pour accéder aux outils de dessin. Les événements JavaScript disponibles pour les canevas sont très simples, contrairement à ceux disponibles pour le format SVG. Tout événement déclenché concerne l'élément dans son ensemble, et non quelque chose dessiné sur le canevas, tout comme un élément d'image normal. Voici un exemple de toile de base:
var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');//Draw a blue rectanglecontext.fillStyle = '#91C0FF';context.fillRect(100, // x100, // y400, // width200 // height);//Draw some textcontext.fillStyle = '#333';context.font = "18px Helvetica, Arial";context.textAlign = 'center';context.fillText("The wonderful world of canvas", // text300, // x200 // y);
C'est assez simple de commencer. La seule chose qui peut être un peu déroutante est que le contexte doit être configuré avec les paramètres tels que fillStyle, lineWidth, font, et strokeStyle avant que l'appel de dessin réel ne soit utilisé. Il est facile d'oublier de mettre à jour ou de réinitialiser ces paramètres et d'obtenir des résultats inattendus.
Le premier exemple ne fonctionnait qu'une seule fois et dessinait une image statique sur le canevas. C'est bien, mais quand ça devient vraiment amusant, c'est quand il est mis à jour à 60 images par seconde. Les navigateurs modernes ont la fonction intégrée requestAnimationFrame qui synchronise le code de dessin personnalisé avec les cycles de dessin du navigateur. Cela aide en termes d'efficacité et de douceur. La cible d'une visualisation doit être un code qui bourdonne à 60 images par seconde.
(Une note sur le support: il y a quelques polyfills simples disponibles si vous avez besoin de supporter les navigateurs plus anciens.)
var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');var counter = 0;var rectWidth = 40;var rectHeight = 40;var xMovement;//Place rectangle in the middle of the screenvar y = ( canvas.height / 2 ) - ( rectHeight / 2 );context.fillStyle = '#91C0FF';function draw() {//There are smarter ways to increment time, but this is for demonstration purposescounter++;//Cool math below. More explanation in the text following the code.xMovement = Math.sin(counter / 25) * canvas.width * 0.4 + canvas.width / 2 - rectWidth / 2;//Clear the previous drawing resultscontext.clearRect(0, 0, canvas.width, canvas.height);//Actually draw on the canvascontext.fillRect(xMovement,y,rectWidth,rectHeight);//Request once a new animation frame is available to call this function againrequestAnimationFrame( draw );}draw();
Maintenant, je réécris ma formule à partir de l'exemple de code précédent en une version plus détaillée, plus facile à lire.
var a = 1 / 25, //Make the oscillation happen a lot slowerx = counter, //Move along the graph a little bit each time draw() is calledb = 0, //No need to adjust the graph up or downc = width * 0.4, //Make the oscillation as wide as a little less than half the canvasd = canvas.width / 2 - rectWidth / 2; //Tweak the position of the rectangle to be centeredxMovement = Math.sin( a * x + b ) * c + d;
Si vous voulez jouer avec le code jusqu'à présent, je suggérerais d'ajouter un mouvement dans la direction y. Essayez de changer les valeurs dans la fonction sin, ou passez à un autre type de fonction pour jouer et voir ce qui se passe.
Au-delà du mouvement de conduite avec les mathématiques, prenez le temps d'imaginer ce que vous pouvez faire avec différents périphériques de saisie utilisateur pour déplacer un carré autour d'une page. Il existe toutes sortes d'options disponibles dans le navigateur, notamment le microphone, la webcam, la souris, le clavier et la manette de jeu. Des options supplémentaires basées sur des plug-ins sont disponibles avec quelque chose comme Leap Motion ou Kinect. En utilisant WebSockets et un serveur, vous pouvez connecter une visualisation à du matériel maison. Connectez un microphone à l'API Web Audio et pilotez vos pixels avec du son. Vous pouvez même créer un détecteur de mouvement à partir d'une webcam et effrayer une école de poissons virtuels (bon, j'ai fait le dernier en Flash il y a cinq ans environ).
Alors maintenant que vous avez votre grande idée, revenons à quelques exemples supplémentaires. Un carré est ennuyeux, laisse tomber l'ante. Tout d'abord, créons une fonction carrée qui peut faire beaucoup. Nous l'appellerons un point. Une chose à faire lorsque vous travaillez avec des objets en mouvement consiste à utiliser des vecteurs plutôt que de séparer les variables x et y. Dans ces exemples de code, j'ai tiré dans la classe vectoriel 3.js. Il est facile à utiliser tout de suite avec vector.x et vector.y, mais il a aussi un tas de méthodes pratiques pour travailler avec eux. Jeter un coup d'œil à les docs pour une plongée plus profonde.
Le code de cet exemple devient un peu plus complexe car il interagit avec des objets, mais cela en vaudra la peine. Consultez l'exemple de code pour voir un nouvel objet Scene qui gère les bases du dessin sur le canevas. Notre nouvelle classe Dot aura un handle vers cette scène pour accéder à toutes les variables comme le contexte de canevas dont elle aura besoin.
function Dot( x, y, scene ) {var speed = 0.5;this.color = '#000000';this.size = 10;this.position = new THREE.Vector2(x,y);this.direction = new THREE.Vector2(speed * Math.random() - speed / 2,speed * Math.random() - speed / 2);this.scene = scene;}
Commencer par le constructeur du Dot configure la configuration de son comportement et définit certaines variables à utiliser. Encore une fois, cela utilise la classe vectorielle three.js. Lors du rendu à 60 images par seconde, il est important de pré-initialiser vos objets et de ne pas en créer de nouveaux lors de l’animation. Cela ronge votre mémoire disponible et peut rendre votre visualisation instable. Notez également comment le point est transmis à une copie de la scène par référence. Cela garde les choses propres.
Dot.prototype = {update : function() {...},draw : function() {...}}
Tout le reste du code sera défini sur l'objet prototype de Dot afin que chaque nouveau point créé ait accès à ces méthodes. Je vais aller fonction par fonction dans l'explication.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Je sépare mon code de tirage de mon code de mise à jour. Cela facilite grandement la maintenance et le réglage de votre objet, tout comme le modèle MVC sépare votre logique de contrôle et de vue. La variable dt est le changement de temps en millisecondes depuis le dernier appel de mise à jour. Le nom est court et court et provient de dérivés de calcul (ne soyez pas effrayés). Ce que cela fait est de séparer votre mouvement de la vitesse de la fréquence d'images. De cette façon, vous n’obtenez pas de ralentissements de style NES lorsque les choses deviennent trop compliquées. Votre mouvement laissera tomber des images si cela fonctionne durement, mais il restera à la même vitesse.
updatePosition : function() {//This is a little trick to create a variable outside of the render loop//It's expensive to allocate memory inside of the loop.//The variable is only accessible to the function below.var moveDistance = new THREE.Vector2();//This is the actual functionreturn function( dt ) {moveDistance.copy( this.direction );moveDistance.multiplyScalar( dt );this.position.add( moveDistance );//Keep the dot on the screenthis.position.x = (this.position.x + this.scene.canvas.width) % this.scene.canvas.width;this.position.y = (this.position.y + this.scene.canvas.height) % this.scene.canvas.height;}}(), //Note that this function is immediately executed and returns a different function
Cette fonction est un peu étrange dans sa structure, mais pratique pour les visualisations. C'est très coûteux d'allouer de la mémoire dans une fonction. La variable moveDistance est définie une fois et réutilisée chaque fois que la fonction est appelée.
Ce vecteur est uniquement utilisé pour aider à calculer la nouvelle position, mais n'est pas utilisé en dehors de la fonction. C'est le premier vecteur mathématique utilisé. En ce moment, le vecteur de direction est multiplié par rapport au changement dans le temps, puis ajouté à la position. À la fin, il y a une petite action modulo pour garder le point à l'écran.
draw : function(dt) {//Get a short variable name for conveniencevar ctx = this.scene.context;ctx.beginPath();ctx.fillStyle = this.color;ctx.fillRect(this.position.x, this.position.y, this.size, this.size);}
Enfin les choses faciles. Obtenez une copie du contexte à partir de l'objet de la scène, puis dessinez un rectangle (ou ce que vous voulez). Les rectangles sont probablement la chose la plus rapide que vous pouvez dessiner sur l'écran.
À ce stade, j'ajoute un nouveau point en appelant this.dot = new Dot (x, y, this) dans le constructeur de la scène principale, puis dans la méthode de mise à jour de la scène, j'ajoute this.dot.update (dt) et il y a un point zoomant sur l'écran. (Consultez le code source pour le code complet en contexte.)
Maintenant, dans la scène, plutôt que de créer et de mettre à jour un point , nous créons et mettons à jour le DotManager . Nous allons créer 5000 points pour commencer.
function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};
C'est un peu déroutant dans une ligne, donc ici il est décomposé comme la fonction de péché de la précédente.
var a = 1 / 500, //Make the oscillation happen a lot slowerx = this.scene.currTime, //Move along the graph a little bit each time draw() is calledb = this.position.x / this.scene.canvas.width * 4, //No need to adjust the graph up or downc = 20, //Make the oscillation as wide as a little less than half the canvasd = 0; //Tweak the position of the rectangle to be centeredxMovement = Math.sin( a * x + b ) * c + d;
Obtenir groovy ...
Encore un petit tweak Le monochrome est un peu terne, alors ajoutons de la couleur.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
Cet objet simple encapsule la logique des mises à jour de la souris à partir du reste de la scène. Il ne met à jour que le vecteur de position lors d'un déplacement de la souris. Les autres objets peuvent alors échantillonner à partir du vecteur de position de la souris s'ils reçoivent une référence à l'objet. Une mise en garde que j'ignore ici est si la largeur du canevas n'est pas un à un avec les dimensions en pixels du DOM, c.-à-d. Une toile redimensionnée ou un canevas de rétine. en haut à gauche. Les coordonnées de la souris devront être ajustées en conséquence.
var Scene = function() {...this.mouse = new Mouse( this );...};
La seule chose qui restait à la souris était de créer l'objet souris à l'intérieur de la scène. Maintenant que nous avons une souris, attirons-les.
function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}
J'ai ajouté des valeurs scalaires au point afin que chacun se comporte un peu différemment dans la simulation pour lui donner un peu de réalisme. Jouez avec ces valeurs pour avoir une sensation différente. Maintenant, pour attirer la méthode de la souris. C'est un peu long avec les commentaires.
attractMouse : function() {//Again, create some private variables for this methodvar vectorToMouse = new THREE.Vector2(),vectorToMove = new THREE.Vector2();//This is the actual public methodreturn function(dt) {var distanceToMouse, distanceToMove;//Get a vector that represents the x and y distance from the dot to the mouse//Check out the three.js documentation for more information on how these vectors workvectorToMouse.copy( this.scene.mouse.position ).sub( this.position );//Get the distance to the mouse from the vectordistanceToMouse = vectorToMouse.length();//Use the individual scalar values for the dot to adjust the distance movedmoveLength = dt * (this.attractDistance - distanceToMouse) / this.attractSpeed;//Only move the dot if it's being attractedif( moveLength > 0 ) {//Resize the vector to the mouse to the desired move lengthvectorToMove.copy( vectorToMouse ).divideScalar( distanceToMouse ).multiplyScalar( moveLength );//Go ahead and add it to the current position now, rather than in the draw callthis.position.add(vectorToMove);}};}()
Cette méthode peut être un peu déroutante si vous n'êtes pas à jour sur vos mathématiques vectorielles. Les vecteurs peuvent être très visuels et peuvent aider si vous dessinez des gribouillis sur un bout de papier taché de café. En anglais simple, cette fonction obtient la distance entre la souris et le point. Il déplace ensuite le point un peu plus près du point en fonction de sa proximité avec le point et du temps écoulé. Cela se fait en calculant la distance à déplacer (un nombre scalaire normal), puis en la multipliant par le vecteur normalisé (un vecteur de longueur 1) du point pointant vers la souris. Ok, cette dernière phrase n’était pas forcément l’anglais, mais c’est un début.