Mooie code is een genot om te schrijven, maar het is moeilijk om die vreugde te delen met andere programmeurs, en niet te vergeten met niet-programmeurs. In mijn vrije tijd tussen mijn dagtaak en mijn familie heb ik gespeeld met het idee van een programmeerdicht met behulp van het canvaselement om in de browser te tekenen. Er zijn veel verschillende termen om visuele experimenten op de computer te beschrijven, zoals dev art, code sketch, demo en interactieve kunst, maar uiteindelijk besloot ik om gedichten te programmeren om dit proces te beschrijven. Het idee achter een gedicht is een gepolijst stukje proza ​​dat gemakkelijk deelbaar, beknopt en esthetisch is. Het is geen half-afgewerkt idee in een schetsboek, maar een samenhangend stuk gepresenteerd aan de kijker voor hun plezier. Een gedicht is geen hulpmiddel, maar bestaat om een ​​emotie op te roepen.

Voor mijn eigen plezier las ik boeken over wiskunde, rekenen, natuurkunde en biologie. Ik heb heel snel geleerd dat wanneer ik op een idee loop, het mensen vrij snel verveelt. Ik kan visueel een aantal van deze ideeën aannemen, die ik fascinerend vind, en snel iemand een gevoel van verwondering geven, zelfs als ze de theorie achter de code en de concepten die ermee werken niet begrijpen. Je hebt geen handvat nodig voor een harde filosofie of wiskunde om een ​​programmeergedicht te schrijven, gewoon een verlangen om iets live te zien en te ademen op het scherm.

De code en voorbeelden die ik hieronder heb opgesteld, zullen een begin maken met het begrijpen hoe dit snelle en zeer bevredigende proces daadwerkelijk kan worden uitgevoerd. Als u de code wilt volgen die u kunt volgen download hier de bronbestanden.

De belangrijkste truc bij het daadwerkelijk maken van een gedicht is om het licht en eenvoudig te houden. Besteed geen drie maanden aan het bouwen van een echt coole demo. Maak in plaats daarvan 10 gedichten die een idee evolueren. Schrijf experimentele code die opwindend is en wees niet bang om te falen.

Introductie tot Canvas

Voor een snel overzicht is het canvas in wezen een 2d bitmap-afbeeldingselement dat in de DOM leeft waarop kan worden getekend. Tekenen kan worden gedaan met behulp van een 2D-context of een WebGL-context. De context is het JavaScript-object dat u gebruikt om toegang te krijgen tot de tekenhulpmiddelen. De JavaScript-gebeurtenissen die beschikbaar zijn voor canvas zijn erg barebones, in tegenstelling tot de beschikbare voor SVG. Elke gebeurtenis die wordt geactiveerd, is voor het element als geheel, niet als iets dat op het canvas wordt getekend, net als bij een normaal afbeeldingselement. Hier is een eenvoudig canvasvoorbeeld:

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

Het is vrij eenvoudig om te beginnen. Het enige dat een beetje verwarrend kan zijn, is dat de context moet worden geconfigureerd met de instellingen zoals fillStyle, lineWidth, font en strokeStyle voordat de daadwerkelijke tekenoproep wordt gebruikt. Het is gemakkelijk om te vergeten deze instellingen bij te werken of opnieuw in te stellen en onbedoelde resultaten te krijgen.

Dingen laten bewegen

Het eerste voorbeeld is maar één keer uitgevoerd en heeft een statische afbeelding op het canvas getekend. Dat is OK, maar als het echt leuk wordt, is het bijgewerkt met 60 frames per seconde. Moderne browsers hebben het ingebouwde functieverzoekAnimationFrame dat de aangepaste tekencode synchroniseert met de trekkingscycli van de browser. Dit helpt in termen van efficiëntie en zachtheid. Het doelwit van een visualisatie moet een code zijn die meegeeft met 60 frames per seconde.

(Een opmerking over ondersteuning: er zijn enkele eenvoudige polyfills beschikbaar als u oudere browsers wilt ondersteunen.)

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

Dat is leuk dat er een beetje meer interne structuur is voor de code, maar het doet echt niets dat veel interessanter is. Dat is waar een loop binnenkomt. In het scene-object zullen we een nieuw DotManager- object maken. Het is handig om deze functionaliteit in een afzonderlijk object te verzamelen, omdat het gemakkelijker en schoner is om te redeneren, omdat er steeds meer complexiteit aan de simulatie wordt toegevoegd.

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 in de scène, in plaats van een punt te maken en bij te werken, maken en updaten we de DotManager . We maken 5000 punten om te beginnen.

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

Maak voor elke nieuwe aangemaakte punt de beginpositie en stel de tint in op de breedte van het canvas. De functie Utils.hslToFillStyle is een kleine helperfunctie die ik heb toegevoegd om sommige invoervariabelen om te zetten in de correct opgemaakte FillStyle- reeks. De dingen zien er al spannender uit. De stippen zullen uiteindelijk samenvloeien en hun regenboogeffect verliezen nadat ze tijd hebben om zich te verspreiden. Nogmaals, dit is een voorbeeld van het besturen van visuals met een beetje wiskunde of variabele ingangen. Ik vind kleuren met het HSL-kleurmodel erg leuk met generatieve kunst in plaats van RGB vanwege het gebruiksgemak. RGB is een beetje abstract.

Gebruikersinteractie met een muis

Er is tot nu toe geen echte gebruikersinteractie geweest.

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

Dit eenvoudige object kapselt de logica van de muisupdates van de rest van de scène in. Het update alleen de positievector bij een muisbeweging. De rest van de objecten kan vervolgens de voorbeeldvector van de muis samplen als ze een verwijzing naar het object hebben doorgegeven. Een waarschuwing die ik hier negeer, is of de breedte van het canvas niet één op één is met de pixeldimensies van de DOM, dat wil zeggen een canvas met een responsief aangepaste grootte of een canvas met een hogere pixeldichtheid (retina) of als het canvas niet bij de linksboven. De coördinaten van de muis moeten dienovereenkomstig worden aangepast.

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

Het enige dat overbleef voor de muis was om het muisobject in de scène te maken. Nu we een muis hebben, trekken we er de puntjes naar toe.

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

Ik heb een aantal scalaire waarden aan de stip toegevoegd zodat elke zich een beetje anders gedraagt ​​in de simulatie om het een beetje realistisch te maken. Speel rond met deze waarden om een ​​ander gevoel te krijgen. Nu verder met de attract muis methode. Het is een beetje lang met de opmerkingen.

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

Deze methode kan een beetje verwarrend zijn als u niet op de hoogte bent van uw vector wiskunde. Vectoren kunnen zeer visueel zijn en kunnen helpen als u wat krabbels op een met koffie bevlekt stukje papier tekent. In gewoon Engels krijgt deze functie de afstand tussen de muis en de stip. Het verplaatst de stip dan iets dichter bij de stip op basis van hoe dicht het al is bij de stip en de hoeveelheid tijd die is verstreken. Het doet dit door de te verplaatsen afstand uit te rekenen (een normaal scalair getal) en dat vervolgens te vermenigvuldigen met de genormaliseerde vector (een vector met lengte 1) van de punt die naar de muis wijst. Ok, die laatste zin was niet altijd duidelijk Engels, maar het is een begin.