Il bello codice è una gioia da scrivere, ma è difficile condividere questa gioia con altri programmatori, per non parlare dei non programmatori. Nel mio tempo libero tra il mio lavoro diurno e il tempo di famiglia, ho giocato con l'idea di un poema di programmazione usando l'elemento canvas per disegnare nel browser. Ci sono una moltitudine di termini là fuori per descrivere esperimenti visivi sul computer come la dev art, lo sketch di codice, la demo e l' arte interattiva, ma alla fine ho deciso di programmare un poema per descrivere questo processo. L'idea alla base di un poema è un raffinato pezzo di prosa che è facilmente condivisibile, conciso ed estetico. Non è un'idea semilavorata in un album da disegno, ma un pezzo coeso presentato allo spettatore per il loro divertimento. Una poesia non è uno strumento, ma esiste per evocare un'emozione.

Per il mio divertimento ho letto libri di matematica, calcolo, fisica e biologia. Ho imparato molto velocemente che quando divago un'idea, annoia le persone abbastanza rapidamente. Visivamente posso prendere alcune di queste idee che trovo affascinanti e dare rapidamente a chiunque un senso di meraviglia, anche se non comprendono la teoria che sta dietro al codice e ai concetti che la guidano. Non hai bisogno di gestire una filosofia o una matematica difficile per scrivere un poema di programmazione, solo il desiderio di vedere qualcosa di vivo e di respirare sullo schermo.

Il codice e gli esempi che ho messo insieme di seguito daranno il via a una comprensione di come effettivamente portare a termine questo processo rapido e altamente soddisfacente. Se vuoi seguire il codice, puoi farlo scarica qui i file sorgente.

Il trucco principale quando si crea effettivamente un poema è di mantenerlo leggero e semplice. Non passare tre mesi a creare una demo davvero interessante. Invece, crea 10 poesie che evolvano un'idea. Scrivi un codice sperimentale che è eccitante e non aver paura di fallire.

Introduzione alla tela

Per una rapida panoramica, la tela è essenzialmente un elemento immagine bitmap 2d che vive nel DOM su cui è possibile disegnare. Il disegno può essere eseguito utilizzando un contesto 2d o un contesto WebGL. Il contesto è l'oggetto JavaScript che si usa per accedere agli strumenti di disegno. Gli eventi JavaScript disponibili per canvas sono molto barebone, a differenza di quelli disponibili per SVG. Qualsiasi evento che viene attivato è per l'elemento nel suo complesso, non qualcosa disegnato sulla tela, proprio come un normale elemento dell'immagine. Ecco un esempio di base della tela:

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);

È abbastanza semplice iniziare. L'unica cosa che potrebbe essere un po 'confusa è che il contesto deve essere configurato con le impostazioni come fillStyle, lineWidth, font e strokeStyle prima che venga utilizzata la chiamata draw effettiva. È facile dimenticare di aggiornare o ripristinare tali impostazioni e ottenere risultati non voluti.

Fare muovere le cose

Il primo esempio è stato eseguito una volta sola e ha disegnato un'immagine statica sulla tela. Va bene, ma quando diventa davvero divertente è quando viene aggiornato a 60 frame al secondo. I browser moderni hanno la funzione incorporata requestAnimationFrame che sincronizza il codice di disegno personalizzato per i cicli di disegno del browser. Questo aiuta in termini di efficienza e scorrevolezza. L'obiettivo di una visualizzazione dovrebbe essere il codice che ronza a 60 fotogrammi al secondo.

(Una nota sul supporto: ci sono alcuni semplici polifragioli disponibili se devi supportare i browser più vecchi.)

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();

È bello che ci sia un po 'più di struttura interna al codice, ma in realtà non fa nulla di molto più interessante. Ecco dove arriva un ciclo. Nell'oggetto scena creeremo un nuovo oggetto DotManager . È utile raccogliere questa funzionalità in un oggetto separato, poiché è più facile e più pulito ragionare in quanto viene aggiunta sempre più complessità alla simulazione.

var DotManager = function( numberOfDots, scene ) {this.dots = [];this.numberOfDots = numberOfDots;this.scene = scene;for(var i=0; i < numberOfDots; i++) {this.dots.push( new Dot(Math.random() * this.canvas.width,Math.random() * this.canvas.height,this.scene));}};DotManager.prototype = {update : function( dt ) {for(var i=0; i < this.numberOfDots; i++) {this.dots[i].update( dt );}}};

Ora nella scena, invece di creare e aggiornare un punto , creiamo e aggiorniamo DotManager . Creeremo 5000 punti per iniziare.

function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};

Per ogni nuovo punto creato, prendi la sua posizione iniziale e imposta la sua tonalità su dove si trova lungo la larghezza della tela. La funzione Utils.hslToFillStyle è una piccola funzione di aiuto che ho aggiunto per trasformare alcune variabili di input nella stringa fillStyle formattata correttamente. Le cose sembrano già più eccitanti. I punti finiranno per fondersi e perdere il loro effetto arcobaleno dopo che avranno avuto il tempo di disperdersi. Ancora una volta, questo è un esempio di guida di elementi visivi con un po 'di matematica o input variabili. Mi piace molto fare i colori con il modello di colore HSL con arte generativa piuttosto che RGB a causa della facilità d'uso. RGB è un po 'astratto.

Interazione dell'utente usando un mouse

Non c'è stata alcuna reale interazione da parte dell'utente fino a questo punto.

var Mouse = function( scene ) {this.scene = scene;this.position = new THREE.Vector2(-10000, -10000);$(window).mousemove( this.onMouseMove.bind(this) );};Mouse.prototype = {onMouseMove : function(e) {if(typeof(e.pageX) == "number") {this.position.x = e.pageX;this.position.y = e.pageY;} else {this.position.x = -100000;this.position.y = -100000;}}};

Questo semplice oggetto incapsula la logica degli aggiornamenti del mouse dal resto della scena. Aggiorna solo il vettore posizione su una mossa del mouse. Il resto degli oggetti può quindi campionare dal vettore di posizione del mouse se viene passato un riferimento all'oggetto. Un avvertimento che sto ignorando qui è se la larghezza della tela non è uno a uno con le dimensioni in pixel del DOM, cioè una tela ridimensionata in modo reattivo o una tela con densità di pixel (retina) più alta o se la tela non si trova sul a sinistra in alto. Le coordinate del mouse dovranno essere regolate di conseguenza.

var Scene = function() {...this.mouse = new Mouse( this );...};

L'unica cosa rimasta per il mouse era creare l'oggetto del mouse all'interno della scena. Ora che abbiamo un mouse, attiriamo i punti.

function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}

Ho aggiunto alcuni valori scalari al punto in modo che ognuno si comporti in modo un po 'diverso nella simulazione per dargli un po' di realismo. Gioca con questi valori per avere una sensazione diversa. Ora vai al metodo attrarre il mouse. È un po 'lungo con i commenti.

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);}};}()

Questo metodo potrebbe essere un po 'confuso se non sei aggiornato sulla matematica vettoriale. I vettori possono essere molto visivi e possono essere d'aiuto se si disegnano degli scarabocchi su un pezzo di carta macchiata di caffè. In inglese semplice, questa funzione sta ottenendo la distanza tra il mouse e il punto. Quindi sposta il punto un po 'più vicino al punto in base a quanto è vicino al punto e alla quantità di tempo trascorso. Lo fa calcolando la distanza da spostare (un normale numero scalare) e quindi moltiplicandolo per il vettore normalizzato (un vettore con lunghezza 1) del punto che punta al mouse. Ok, quell'ultima frase non era necessariamente un inglese semplice, ma è un inizio.