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.

Intro til lærred

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.

Gør ting flytte

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

Det er godt, at der er en smule mere intern struktur til koden, men det gør ikke rigtig noget, der er meget mere interessant. Det er her en loop kommer ind. I sceneobjektet opretter vi et nyt DotManager- objekt. Det er praktisk at indsamle denne funktionalitet i en separat genstand, da det er nemmere og renere at begrunde med, da mere og mere kompleksitet bliver tilføjet til simuleringen.

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

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

For hver nye prik skabt skal du tage sin startposition og indstille sin nuance til, hvor den ligger langs lærredets bredde. Funktionen Utils.hslToFillStyle er en lille hjælperfunktion, jeg tilføjede til at omdanne nogle inputvariabler til den korrekt formaterede fillStyle- streng. Allerede ser det mere spændende ud. Punkterne vil i sidste ende fusionere sammen og miste deres regnbueffekt, efter at de har tid til at sprede sig. Igen er dette et eksempel på kørselsbilleder med lidt matematiske eller variable input. Jeg nyder virkelig at lave farver med HSL-farvemodellen med generativ kunst frem for RGB på grund af brugervenligheden. RGB er lidt abstrakt.

Brugerinteraktion ved hjælp af en mus

Der har ikke været nogen reel brugerinteraktion op til dette punkt.

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

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.