Güzel kod yazmanın bir neşesidir, ancak programcı olmayanlarla bahsetmemek için diğer programcılarla bu neşeyi paylaşmak zordur. Gündelik işimle aile zamanım arasındaki boş zamanlarımda, tarayıcıda çizim yapmak için tuval öğesini kullanarak bir programlama şiiri düşüncesiyle uğraştım. Bilgisayarda dev sanat, kod çizimi, demo ve etkileşimli sanat gibi görsel deneyleri tanımlamak için çok sayıda terim var , ama sonuçta bu süreci tanımlamak için programlama şiirine yerleştim. Bir şiirin ardındaki fikir, kolayca paylaşılabilir, özlü ve estetik olan, cilalanmış bir düzyazı parçasıdır. Bir eskiz kitabında yarı bitmiş bir fikir değil, izleyicilere keyif için sunulan bir parça. Bir şiir bir araç değildir, ama bir duygu uyandırmak için vardır.

Kendi zevkim için matematik, hesaplama, fizik ve biyoloji üzerine kitaplar okuyorum. Gerçekten çok çabuk öğrendim ki bir fikre ramble ettiğimde insanlar çok çabuk sıkıyor. Görsel olarak, büyüleyici bulduğum bu fikirlerden bazılarını alıp, kodun ve onu yönlendiren kavramların ardındaki teoriyi anlamadıkları halde, herkese bir şaşkınlık hissi verebilirim. Bir programlama şiiri yazmak için herhangi bir sabit felsefe ya da matematikte bir ele ihtiyaç duymazsınız, sadece bir şeyleri canlı görmek ve ekranda nefes almak arzusu.

Aşağıda bir araya getirdiğim kod ve örnekler, bu hızlı ve son derece tatmin edici sürecin nasıl çekileceğini anlamaya başlayacak. Kod ile birlikte takip etmek isterseniz Kaynak dosyaları buradan indirebilirsiniz.

Aslında bir şiir oluştururken ana hilesi onu hafif ve basit tutmaktır. Gerçekten havalı bir demoda üç ay geçirme. Bunun yerine, bir fikir geliştiren 10 şiir yaratın. Heyecan verici deneysel kod yazın ve başarısız olmaktan korkmayın.

Tuvale Giriş

Hızlı bir genel bakış için, tuval esasen çekilebilecek DOM'de yaşayan bir 2d bitmap görüntü öğesidir. Çizim, 2 boyutlu bir bağlam veya bir WebGL bağlamı kullanılarak yapılabilir. Bağlam, çizim araçlarına erişmek için kullandığınız JavaScript nesnesidir. Tuval için geçerli olan JavaScript olayları, SVG için mevcut olanlardan farklı olarak çok barebone. Tetiklenen herhangi bir olay, normal bir görüntü elemanı gibi, bir bütün olarak, tuval üzerine çizilen bir şey değildir. İşte temel bir tuval örneği:

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

Başlamak için oldukça basit. Biraz kafa karıştırıcı olabilecek tek şey, bağlamın, gerçek çizim çağrısı kullanılmadan önce fillStyle, lineWidth, font ve strokeStyle gibi ayarlarla yapılandırılması gerektiğidir. Bu ayarları güncellemeyi veya sıfırlamayı ve bazı istenmeyen sonuçları almayı unutmak çok kolay.

İşleri hareket ettirmek

İlk örnek sadece bir kez koştu ve tuval üzerine statik bir görüntü çizdi. Tamam, ama gerçekten eğlenceli olduğunda, saniyede 60 kare güncellendiğinde. Modern tarayıcılar, özel çizim kodunu tarayıcının çizim döngüleriyle eşzamanlayan yerleşik fonksiyon requestAnimationFrame'e sahiptir. Bu, verimlilik ve yumuşaklık açısından yardımcı olur. Bir görselleştirmenin hedefi, saniyede 60 kare boyunca uzanan kod olmalıdır.

(Destekle ilgili bir not: daha eski tarayıcıları desteklemeniz gerektiğinde bazı basit çoklu dolgular mevcuttur.)

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

Bu, kod için biraz daha içsel bir yapıya sahip olmak güzel, ama gerçekten çok daha ilginç olan hiçbir şey yapmıyor. Bir döngü geldiği yer burası. Sahne nesnesinde yeni bir DotManager nesnesi oluşturacağız. Bu işlevselliği ayrı bir nesnede toplamak daha kolaydır, çünkü simülasyona daha fazla karmaşıklık eklendikçe neden daha kolay ve daha temizdir.

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

Şimdi sahnede, bir Dot yaratıp güncellemek yerine DotManager'ı yaratıp güncelleriz. Başlamak için 5000 nokta oluşturacağız.

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

Oluşturulan her yeni Nokta için, ilk konumunu alın ve tonunu, tuvalin genişliği boyunca olduğu yere ayarlayın. Utils.hslToFillStyle işlevi, bazı giriş değişkenlerini doğru biçimlendirilmiş fillStyle dizesine dönüştürmek için eklediğim küçük bir yardımcı işlevdir. Zaten işler daha heyecan verici görünüyor. Noktalar sonunda bir araya gelecek ve dağılma zamanı olduktan sonra gökkuşağı etkilerini kaybedeceklerdir. Yine bu, görselleri biraz matematik veya değişken girdilerle sürmenin bir örneğidir. Kullanım kolaylığı nedeniyle RGB'den ziyade generatif bir sanatla HSL renk modeli ile renk yapmaktan keyif alıyorum. RGB biraz soyut.

Fare kullanarak kullanıcı etkileşimi

Bu noktaya kadar gerçek bir kullanıcı etkileşimi olmadı.

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

Bu basit nesne, fare güncellemelerinin mantığını sahnenin geri kalanından kapsüller. Sadece bir fare hareketi ile pozisyon vektörünü günceller. Nesnelerin geri kalanı, nesneye bir referans geçtikten sonra farenin pozisyon vektöründen numune alabilirler. Burada görmezden geldiğim bir uyarı, kanvasın genişliğinin DOM'ın piksel boyutlarıyla, yani yeniden boyutlandırılan bir tuval veya daha yüksek bir piksel yoğunluğu (retina) tuvaliyle bire bir olup olmaması veya tuvalin Sol üst. Farenin koordinatlarının buna göre ayarlanması gerekir.

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

Fare için kalan tek şey, sahnenin içindeki fare nesnesini yaratmaktı. Şimdi bir fareye sahip olduğumuza göre, noktaları ona çekelim.

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

Ben noktaya bazı skaler değerler ekledim, böylece her biri simülasyonda biraz farklı gerçekçilik vermesi için biraz farklı davrandı. Farklı bir fikir edinmek için bu değerlerle oynayın. Şimdi çekmek fare yöntemi. Yorumlar biraz uzun.

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

Vektör matematiğinize uymuyorsanız, bu yöntem biraz kafa karıştırıcı olabilir. Vektörler çok görsel olabilir ve eğer kahve lekeli bir kâğıt parçasının üzerine bir karalama çizerseniz yardımcı olabilirler. Düz İngilizce'de, bu işlev fare ile nokta arasındaki mesafeyi elde etmektir. Daha sonra noktaya ne kadar yakın olduğuna ve geçen zaman miktarına bağlı olarak noktayı biraz daha yakın noktaya hareket ettirir. Bu, hareket mesafesini (normal bir skaler sayı) belirleyerek ve ardından, fareye işaret eden noktanın normalleştirilmiş vektörü (1 uzunluğundaki bir vektör) ile çarparak yapar. Tamam, bu son cümle mutlaka İngilizce değildi, ama bu bir başlangıç.