Smuk kode er en glæde at skrive, men det er svært at dele den glæde med andre programmører, for ikke at nævne med ikke-programmører. På min fritid mellem mit dagjob og familietid har jeg spillet rundt med ideen om et programmeringsdigt ved at bruge lærredelementet til at trække i browseren. Der er et væld af udtryk derude for at beskrive visuelle eksperimenter på computeren som dev art, kode skitse, demo og interaktiv kunst, men i sidste ende satte jeg mig på programmeringsdigt for at beskrive denne proces. Ideen bag et digt er et poleret stykke prosa, der nemt kan deles, koncis og æstetisk. Det er ikke en halvfærdig ide i en skitsebog, men et sammenhængende stykke præsenteret for seeren for deres nydelse. Et digt er ikke et redskab, men eksisterer for at fremkalde en følelse.
For min egen fornøjelse har jeg læst bøger om matematik, beregning, fysik og biologi. Jeg har meget hurtigt lært, at når jeg slår på en ide, så borer det folk ret hurtigt. Visuelt kan jeg tage nogle af disse ideer, som jeg finder fascinerende og hurtigt give nogen en følelse af under, selvom de ikke forstår teorien bag koden og begreberne der driver det. Du behøver ikke et håndtag på en hård filosofi eller matematik til at skrive et programmeringsdigt, bare et ønske om at se noget, levende og ånde på skærmen.
Koden og eksemplerne, jeg har lavet sammen nedenfor, vil kickstart en forståelse for, hvordan man rent faktisk trækker denne hurtige og yderst tilfredsstillende proces ud. Hvis du gerne vil følge med den kode, du kan download kildefilerne her.
Det vigtigste trick, når der faktisk oprettes et digt, er at holde det let og enkelt. Brug ikke tre måneder til at bygge en rigtig cool demo. Opret i stedet 10 digte, der udvikler en ide. Skriv eksperimentel kode, der er spændende, og vær ikke bange for at mislykkes.
For et hurtigt overblik er lærredet i det væsentlige et 2d bitmap billedelement, der lever i DOM, der kan trækkes på. Tegning kan udføres med enten en 2d kontekst eller en WebGL kontekst. Konteksten er det JavaScript-objekt, du bruger til at få adgang til tegningsværktøjerne. JavaScript-begivenhederne, der er tilgængelige for lærred, er meget barebones, i modsætning til dem, der er tilgængelige for SVG. Enhver begivenhed, der udløses, er for elementet som helhed, ikke noget, der trækkes på lærredet, ligesom et normalt billedelement. Her er et grundlæggende lærred eksempel:
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);
Det er ret nemt at komme i gang. Det eneste, der kan være lidt forvirrende, er, at konteksten skal konfigureres med indstillingerne som fillStyle, lineWidth, font og strokeStyle, før det faktiske tegneopkald bruges. Det er nemt at glemme at opdatere eller nulstille disse indstillinger og få nogle utilsigtede resultater.
Det første eksempel løber kun én gang og tegner et statisk billede på lærredet. Det er ok, men når det virkelig bliver sjovt, er det opdateret med 60 billeder pr. Sekund. Moderne browsere har den indbyggede funktionsforespørgselAnimationFrame, der synkroniserer brugerdefineret tegningskode til browserens trækcykler. Dette hjælper med hensyn til effektivitet og glathed. Målet med en visualisering bør være kode, der blæser sammen med 60 billeder pr. Sekund.
(En note om support: Der findes nogle enkle polyfilier, hvis du har brug for at understøtte ældre browsere.)
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();
Nu skal jeg omskrive min formel fra det foregående kodeeksempel som en mere brudt version, der er lettere at læse.
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;
Hvis du vil leve med koden hidtil, vil jeg foreslå at tilføje lidt bevægelse i y-retningen. Prøv at ændre værdierne i syndfunktionen, eller skift til en anden form for funktion for at spille rundt og se, hvad der sker.
Ud over kørselsbevægelse med matematik, tag et øjeblik til at forestille dig, hvad du kan gøre med forskellige brugerindgangsenheder til at flytte en firkant omkring en side. Der findes alle mulige muligheder i browseren, herunder mikrofonen, webcam, mus, tastatur og gamepad. Yderligere plugin-drevne muligheder er tilgængelige med noget som Leap Motion eller Kinect. Ved hjælp af WebSockets og en server kan du tilslutte en visualisering til hjemmebaseret hardware. Tilslut en mikrofon til Web Audio API og drev dine pixels med lyd. Du kan endda bygge en bevægelsesføler ud af et webcam og skræmme en skole med virtuel fisk (ok jeg gjorde den sidste i Flash for fem eller så år siden.)
Så nu hvor du har din store ide, lad os hoppe tilbage til nogle flere eksempler. Et firkant er kedeligt, lad os gå ante. Lad os først lave en firkantet funktion, der kan gøre meget. Vi kalder det en prik. En ting der hjælper, når man arbejder med bevægelige objekter, er at bruge vektorer frem for at adskille x og y-variabler. I disse kodeprøver har jeg trukket i three.js Vector2-klassen. Det er nemt at bruge med det samme med vector.x og vector.y, men det har også en masse praktiske metoder til at arbejde med dem. Se på docs for et dybere dykke.
Dette eksempels kode bliver lidt mere komplekst, fordi det interagerer med objekter, men det vil være det værd. Se eksemplet kode for at se et nyt Scene objekt, der styrer det grundlæggende ved at tegne på lærredet. Vores nye prik klasse vil få et håndtag til denne scene for at få adgang til eventuelle variabler som lærredet kontekst, det vil have brug for.
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;}
Til at begynde med opsætter konstruktøren til prikken konfigurationen af dens adfærd og angiver nogle variabler, der skal bruges. Igen bruger dette tre.js vektorklassen. Når du gengiver ved 60fps, er det vigtigt at pre-initialisere dine objekter, og ikke oprette nye mens du animerer. Dette spiser i din ledige hukommelse og kan gøre din visualisering hakket. Bemærk også, hvordan prikken er bestået en kopi af scenen ved reference. Dette holder tingene rene.
Dot.prototype = {update : function() {...},draw : function() {...}}
Hele resten af koden vil blive indstillet på prikken prototype objekt, så hver nye prik, der bliver oprettet, har adgang til disse metoder. Jeg vil fungere efter funktion i forklaringen.
update : function( dt ) {this.updatePosition( dt );this.draw( dt );},
Jeg adskiller min tegningskode fra min opdateringskode. Dette gør det meget lettere at vedligeholde og finjustere dit objekt, ligesom MVC-mønsteret adskiller din kontrol og ser logikken. Dt- variablen er ændring i tid i millisekunder siden sidste opdateringsopkald. Navnet er pænt og kort og kommer fra (ikke være bange) calculus derivater. Hvad dette gør, adskiller din bevægelse ud fra hastigheden af billedfrekvensen. På denne måde får du ikke NES-stil-afmatninger, når tingene bliver for komplicerede. Din bevægelse vil droppe rammer, hvis det virker hårdt, men det vil forblive i samme hastighed.
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
Denne funktion er lidt underlig i sin struktur, men praktisk til visualiseringer. Det er virkelig dyrt at allokere hukommelse i en funktion. Flytningsvariabelen er indstillet en gang og genbruges, når funktionen kaldes.
Denne vektor bruges kun til at beregne den nye position, men bruges ikke uden for funktionen. Dette er den første vektormatematik, der bruges. Lige nu multipliceres retningsvektoren mod tidsforandringen og tilføjes derefter til positionen. I slutningen er der en lille modulo handling foregår for at holde prikken på skærmen.
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);}
Endelig de lette ting. Få en kopi af konteksten fra sceneobjektet, og træk derefter et rektangel (eller hvad du vil). Rektangler er nok den hurtigste ting, du kan tegne på skærmen.
På dette tidspunkt tilføjer jeg en ny prik ved at kalde this.dot = ny prik (x, y, dette) i hovedscene-konstruktøren, og derefter i scenen opdateringsmetode tilføjer jeg en this.dot.update (dt) og der er en prik zoome rundt på skærmen. (Se kildekoden for den fulde kode i kontekst.)
Nu i scenen, i stedet for at oprette og opdatere en prik , opretter og opdaterer vi DotManager . Vi opretter 5000 prikker for at komme i gang.
function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};
Det er lidt forvirrende i en linje, så her er det nedbrudt som syndfunktionen fra tidligere.
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;
Bliver groovy ...
Endnu lidt lille tweak. Monokrom er en lille trist, så lad os tilføje lidt farve.
var hue = this.position.x / this.scene.canvas.width * 360;this.color = Utils.hslToFillStyle(hue, 50, 50, 0.5);
Denne enkle genstand indkapsler logikken i musens opdateringer fra resten af scenen. Det opdaterer kun positionsvektoren på et musebevæg. Resten af objekterne kan så prøve fra musens positionsvektor, hvis de er bestået en reference til objektet. Én advarsel, som jeg ignorerer her, er, hvis lærredets bredde ikke er en til en med DIM-pixeldimensionerne, dvs. et responsivt ændret lærred eller en højere pixeldensitet (nethinden) lærred eller hvis lærredet ikke er placeret på øverst til venstre. Koordinaterne af musen skal justeres i overensstemmelse hermed.
var Scene = function() {...this.mouse = new Mouse( this );...};
Det eneste der blev tilbage til musen var at skabe musemålet inde i scenen. Nu hvor vi har en mus, lad os tiltrække prikkerne til den.
function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}
Jeg tilføjede nogle skalære værdier til prikken, så hver enkelt opfører sig lidt forskelligt i simuleringen for at give den en smule realisme. Spil rundt med disse værdier for at få en anden følelse. Nu på tiltrækningsmusemetoden. Det er lidt længe med kommentarerne.
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);}};}()
Denne metode kan være lidt forvirrende, hvis du ikke er opdateret på din vektormatematik. Vektorer kan være meget visuelle og kan hjælpe, hvis du trækker nogle scribbles ud på et kaffe farvet papirskrot. I almindelig engelsk får denne funktion afstanden mellem musen og prikken. Derefter flytter prikken lidt tættere på prikken baseret på hvor tæt den allerede er til prikken, og hvor lang tid der er gået. Det gør dette ved at finde ud af afstanden til at flytte (et normalt skalarantal) og derefter multiplicere det med den normaliserede vektor (en vektor med længde 1) af prikken, der peger mod musen. Ok, den sidste sætning var ikke nødvendigvis ren engelsk, men det er en start.