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.
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.
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();
Ora riscriverò la mia formula dall'esempio di codice precedente come una versione più dettagliata che è più facile da leggere.
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;
Se vuoi giocare con il codice fino ad ora, ti suggerirei di aggiungere qualche movimento nella direzione y. Prova a cambiare i valori nella funzione sin, o passa a qualche altro tipo di funzione per giocare e vedere cosa succede.
Oltre a guidare il movimento con la matematica, prenditi un momento per immaginare cosa puoi fare con diversi dispositivi di input dell'utente per spostare un quadrato attorno a una pagina. Ci sono tutti i tipi di opzioni disponibili nel browser tra cui microfono, webcam, mouse, tastiera e gamepad. Ulteriori opzioni basate su plugin sono disponibili con qualcosa come Leap Motion o Kinect. Utilizzando WebSockets e un server è possibile collegare una visualizzazione all'hardware di casa. Collega un microfono all'API Web Audio e guida i tuoi pixel con l'audio. Puoi perfino costruire un sensore di movimento da una webcam e spaventare una scuola di pesci virtuali (ok, l'ho fatto l'ultima volta in Flash cinque anni fa).
Quindi ora che hai la tua grande idea, torniamo indietro ad altri esempi. Un quadrato è noioso, alziamo la posta. Prima di tutto, creiamo una funzione quadrata che può fare molto. Lo chiameremo punto. Una cosa che aiuta quando si lavora con gli oggetti in movimento è usare i vettori piuttosto che separare le variabili x e y. In questi esempi di codice ho inserito la classe Vector. three.js. È facile da usare subito con vector.x e vector.y, ma ha anche un sacco di metodi utili per lavorare con loro. Dare un'occhiata a i documenti per un'immersione più profonda.
Il codice di questo esempio diventa un po 'più complesso perché interagisce con gli oggetti, ma ne varrà la pena. Controlla il codice di esempio per vedere un nuovo oggetto Scene che gestisce le basi del disegno sull'area di disegno. La nostra nuova classe Dot otterrà un controllo su questa scena per accedere a qualsiasi variabile come il contesto di canvas di cui avrà bisogno.
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;}
Per iniziare, il costruttore del punto imposta la configurazione del suo comportamento e imposta alcune variabili da utilizzare. Di nuovo, questo sta usando la classe vector three.js. Quando si esegue il rendering a 60 fps, è importante pre-inizializzare gli oggetti e non crearne di nuovi durante l'animazione. Questo mangia nella tua memoria disponibile e può rendere la tua visualizzazione instabile. Inoltre, nota come il punto viene passato una copia della scena per riferimento. Ciò mantiene le cose pulite.
Dot.prototype = {update : function() {...},draw : function() {...}}
Tutto il resto del codice sarà impostato sull'oggetto prototipo di Dot in modo che ogni nuovo Punto creato venga ad accedere a questi metodi. Andrò funzione per funzione nella spiegazione.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Sto separando il mio codice di estrazione dal mio codice di aggiornamento. Ciò rende molto più semplice mantenere e modificare il tuo oggetto, proprio come il pattern MVC separa il tuo controllo e visualizza la logica. La variabile dt è il cambio di tempo in millisecondi dall'ultima chiamata di aggiornamento. Il nome è bello e breve e deriva da derivati del calcolo (non avere paura). Ciò che fa è separare il tuo movimento dalla velocità del frame rate. In questo modo non si ottengono rallentamenti di stile NES quando le cose si complicano troppo. Il tuo movimento farà cadere i fotogrammi se sta lavorando sodo, ma rimarrà alla stessa velocità.
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
Questa funzione è un po 'strana nella sua struttura, ma utile per le visualizzazioni. È molto costoso allocare memoria in una funzione. La variabile moveDistance viene impostata una volta e riutilizzata ogni volta che viene chiamata la funzione.
Questo vettore viene utilizzato solo per aiutare a calcolare la nuova posizione, ma non utilizzato al di fuori della funzione. Questa è la prima matematica vettoriale che viene utilizzata. In questo momento il vettore di direzione viene moltiplicato rispetto al cambiamento nel tempo, quindi aggiunto alla posizione. Alla fine c'è una piccola azione modulo in corso per mantenere il punto sullo schermo.
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);}
Finalmente le cose facili. Prendi una copia del contesto dall'oggetto scena, quindi disegna un rettangolo (o qualsiasi altra cosa desideri). I rettangoli sono probabilmente la cosa più rapida che puoi disegnare sullo schermo.
A questo punto aggiungo un nuovo punto chiamando this.dot = new Dot (x, y, this) nel costruttore della scena principale, e poi nel metodo di aggiornamento delle scene aggiungo un this.dot.update (dt) e c'è un punto che zuma sullo schermo. (Controlla il codice sorgente per il codice completo nel contesto.)
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 );}...};
È un po 'confusionario in una riga, quindi qui è suddiviso come la funzione sin da prima.
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;
Ottenere groovy ...
Un altro piccolo ritocco. Il monocromatico è un po 'triste, quindi aggiungiamo un po' di colore.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
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.