Vacker kod är en glädje att skriva, men det är svårt att dela den glädjen med andra programmerare, för att inte tala med icke-programmerare. På min fritid mellan mitt dagjobb och familjetid har jag spelat med idén om en programmeringsdikt med hjälp av dukelementet för att rita i webbläsaren. Det finns en mängd termer där ute för att beskriva visuella experiment på datorn som dev art, kodskiss, demo och interaktiv konst, men i slutändan bestämde jag mig för programmeringsdikt för att beskriva denna process. Tanken bakom en dikt är ett polerat stycke prosa som lätt kan delas, koncis och estetiskt. Det är inte en halvfärdig idé i en skissbok, men en sammanhållen bit presenterad för tittaren för deras njutning. En dikt är inte ett verktyg, men existerar för att framkalla en känsla.

För min egen njutning har jag läst böcker om matematik, beräkning, fysik och biologi. Jag har lärt mig riktigt snabbt att när jag rysar på en idé borar det människor ganska snabbt. Visuellt kan jag ta några av dessa idéer som jag tycker är fascinerande och snabbt ge någon en känsla av underverk, även om de inte förstår teorin bakom koden och begreppen som driver den. Du behöver inget handtag på någon hård filosofi eller matematik för att skriva en programmeringsdikt, bara en önskan att se något leva och andas på skärmen.

Koden och exemplen som jag har sammanställt nedan kommer att starta en förståelse för hur man faktiskt tar bort denna snabba och mycket tillfredsställande process. Om du vill följa med koden du kan ladda ner källfilerna här.

Det viktigaste tricket när man faktiskt skapar en dikt är att hålla det lätt och enkelt. Spendera inte tre månader på att bygga en riktigt cool demo. Istället skapar 10 dikter som utvecklar en idé. Skriv experimentell kod som är spännande, och var inte rädd för att misslyckas.

Intro till Canvas

För en snabb överblick är duken väsentligen ett 2d bitmap bildelement som bor i DOM som kan dras in på. Ritning kan göras med hjälp av antingen ett 2d-kontext eller ett WebGL-kontext. Kontextet är det JavaScript-objekt som du använder för att få tillgång till ritverktygen. JavaScript-händelserna som finns tillgängliga för duk är mycket barebones, till skillnad från de som är tillgängliga för SVG. Varje händelse som utlöses är för elementet som helhet, inte något som draget på duken, precis som ett normalt bildelement. Här är ett basalt lerret exempel:

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 är ganska enkelt att komma igång. Det enda som kan vara lite förvirrande är att sammanhanget måste konfigureras med inställningarna som fillStyle, lineWidth, font och strokeStyle innan det aktuella rita samtalet används. Det är lätt att glömma att uppdatera eller återställa dessa inställningar och få några oavsiktliga resultat.

Få saker att röra sig

Det första exemplet körde bara en gång och ritade en statisk bild på duken. Det är okej, men när det blir roligt är det när det uppdateras med 60 bilder per sekund. Moderna webbläsare har den inbyggda funktionsförfråganAnimationFrame som synkroniserar anpassad ritningskod till webbläsarens rita cykler. Detta bidrar till effektivitet och jämnhet. Målet för en visualisering bör vara kod som längtar vid 60 bilder per sekund.

(En anteckning om support: det finns några enkla polyprofiler tillgängliga om du behöver stödja äldre webbläsare.)

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 är trevligt att det finns lite mer intern struktur till koden, men det gör inte riktigt något som är mycket mer intressant. Det är där en slinga kommer in. I scenobjektet skapar vi ett nytt DotManager- objekt. Det är praktiskt att samla denna funktionalitet i ett separat objekt, eftersom det är enklare och renare att motivera med, eftersom allt mer komplexitet läggs till i 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, istället för att skapa och uppdatera en prick , skapar och uppdaterar vi DotManager . Vi skapar 5000 prickar för att komma igång.

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

För varje ny Dot skapad, ta dess ursprungliga position och ställ in nyans till var den ligger längs bredden på duken. Funktionen Utils.hslToFillStyle är en liten hjälpfunktion som jag lagt till för att omvandla vissa inmatningsvariabler till den korrekt formaterade fillStyle- strängen. Redan ser sakerna mer spännande ut. Punkterna kommer så småningom att slå samman och förlora sin regnbågseffekt när de har tid att sprida sig. Återigen är detta ett exempel på körbilder med lite matematiska eller variabla ingångar. Jag tycker verkligen om att göra färger med HSL-färgmodellen med generativ konst snarare än RGB på grund av användarvänligheten. RGB är lite abstrakt.

Användarinteraktion med en mus

Det har inte funnits någon verklig användarinteraktion fram till den här punkten.

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

Detta enkla objekt inkapslar logiken för musuppdateringarna från resten av scenen. Det uppdaterar bara positionsvektorn på ett musfönster. Resten av objekten kan sedan prova från musens positionsvektor om de skickas en referens till objektet. En försiktighet som jag ignorerar här är om dukens bredd inte är en till en med DIM-pixeldimensionerna, dvs en måttlig storlek på kanvas eller en högre pixeldensitet (näthinnan) eller om duken inte finns på övre vänstra. Musens koordinater behöver justeras i enlighet därmed.

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

Det enda som lämnades för musen var att skapa musobjektet inne i scenen. Nu när vi har en mus, låt oss attrahera prickarna till den.

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

Jag lade till några skalärvärden till pricken så att varje uppför sig lite annorlunda i simuleringen för att ge den lite realism. Spela runt med dessa värden för att få en annan känsla. Nu vidare till attraktiva musmetoden. Det är lite länge med kommentarerna.

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

Denna metod kan vara lite förvirrande om du inte är uppdaterad på din vektormatris. Vektorer kan vara väldigt visuella och kan hjälpa dig om du drar några skribbar ut på ett kaffefärgat pappersskrot. På vanlig engelska får denna funktion avståndet mellan musen och pricken. Därefter flyttar punkten lite närmare punkten baserat på hur nära det redan är till pricken och hur lång tid det har gått. Det gör det genom att bestämma avståndet att flytta (ett normalt skalärt tal) och sedan multiplicera det med den normaliserade vektorn (en vektor med längd 1) av punkten som pekar mot musen. Ok, den sista meningen var inte nödvändigtvis vanlig engelska, men det är en början.