Código bonito é uma alegria para escrever, mas é difícil compartilhar essa alegria com outros programadores, sem mencionar com os não-programadores. No meu tempo livre entre o meu dia de trabalho e tempo de família eu tenho brincado com a idéia de um poema de programação usando o elemento de tela para desenhar no navegador. Há uma infinidade de termos lá fora para descrever experimentos visuais no computador, como dev art, sketch de código, demo e arte interativa, mas , no final das contas, decidi por um poema de programação para descrever esse processo. A ideia por trás de um poema é uma peça polida de prosa que é facilmente compartilhável, concisa e estética. Não é uma ideia incompleta em um caderno de esboços, mas uma peça coesa é apresentada ao espectador para sua diversão. Um poema não é uma ferramenta, mas existe para evocar uma emoção.

Para meu próprio prazer, tenho lido livros sobre matemática, computação, física e biologia. Eu aprendi muito rapidamente que quando eu divagar sobre uma idéia, isso aborrece as pessoas rapidamente. Visualmente, posso pegar algumas dessas idéias que considero fascinantes e rapidamente dar a qualquer um a sensação de admiração, mesmo que não entendam a teoria por trás do código e dos conceitos que a impulsionam. Você não precisa lidar com nenhuma filosofia ou matemática difícil para escrever um poema de programação, apenas um desejo de ver algo ao vivo e respirar na tela.

O código e os exemplos que eu apresento abaixo irão dar um passo em frente na compreensão de como realmente realizar esse processo rápido e altamente satisfatório. Se você gostaria de seguir junto com o código que você pode baixe os arquivos de origem aqui.

O principal truque ao criar um poema é mantê-lo leve e simples. Não gaste três meses fazendo uma demonstração muito legal. Em vez disso, crie 10 poemas que evoluem uma ideia. Escreva um código experimental que seja excitante e não tenha medo de falhar.

Introdução à tela

Para uma rápida visão geral, a tela é essencialmente um elemento de imagem bitmap 2d que vive no DOM que pode ser desenhado para. O desenho pode ser feito usando um contexto 2d ou um contexto WebGL. O contexto é o objeto JavaScript que você usa para obter acesso às ferramentas de desenho. Os eventos JavaScript que estão disponíveis para a tela são muito barebones, ao contrário dos disponíveis para o SVG. Qualquer evento que é acionado é para o elemento como um todo, não qualquer coisa desenhada na tela, como um elemento de imagem normal. Aqui está um exemplo básico de tela:

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

É bastante simples de começar. A única coisa que pode ser um pouco confusa é que o contexto deve ser configurado com as configurações fillStyle, lineWidth, font e strokeStyle antes que a chamada de desenho real seja usada. É fácil esquecer de atualizar ou redefinir essas configurações e obter alguns resultados indesejados.

Fazendo as coisas se moverem

O primeiro exemplo foi executado apenas uma vez e desenhou uma imagem estática na tela. Tudo bem, mas quando realmente fica divertido é quando é atualizado a 60 quadros por segundo. Os navegadores modernos têm a função interna requestAnimationFrame que sincroniza o código de desenho personalizado para os ciclos de desenho do navegador. Isso ajuda em termos de eficiência e suavidade. O alvo de uma visualização deve ser o código que cantarola a 60 quadros por segundo.

(Uma nota sobre o suporte: há alguns polyfills simples disponíveis se você precisar dar suporte a navegadores mais antigos).

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

Que bom que há um pouco mais de estrutura interna no código, mas não faz nada muito mais interessante. É aí que entra um loop. No objeto de cena, vamos criar um novo objeto DotManager . É útil coletar essa funcionalidade em um objeto separado, pois é mais fácil e mais claro raciocinar à medida que mais e mais complexidade são adicionadas à simulação.

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

Agora, na cena, em vez de criar e atualizar um ponto , criamos e atualizamos o DotManager . Vamos criar 5000 pontos para começar.

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

Para cada novo Dot criado, tome sua posição inicial e defina sua matiz para onde ela está ao longo da largura da tela. A função Utils.hslToFillStyle é uma pequena função auxiliar que adicionei para transformar algumas variáveis ​​de entrada na cadeia fillStyle formatada corretamente. As coisas já estão parecendo mais excitantes. Os pontos eventualmente se fundirão e perderão o efeito de arco-íris depois que tiverem tempo de se dispersar. Mais uma vez, este é um exemplo de direcionar visuais com um pouco de matemática ou entradas variáveis. Eu realmente gosto de fazer cores com o modelo de cores HSL com arte generativa, em vez de RGB, devido à facilidade de uso. RGB é um pouco abstrato.

Interação do usuário usando um mouse

Não houve interação real do usuário até este ponto.

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

Esse objeto simples encapsula a lógica das atualizações do mouse do resto da cena. Apenas atualiza o vetor de posição em um movimento do mouse. O restante dos objetos pode, então, fazer uma amostra do vetor de posição do mouse, se eles tiverem passado por uma referência ao objeto. Uma ressalva que estou ignorando aqui é se a largura da tela não é uma para uma com as dimensões em pixels do DOM, ou seja, uma tela redimensionada responsavelmente ou uma tela de densidade de pixels (retina) maior ou se a tela não estiver localizada na tela canto superior esquerdo. As coordenadas do mouse precisarão ser ajustadas de acordo.

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

A única coisa que restou para o mouse foi criar o objeto mouse dentro da cena. Agora que temos um mouse, vamos atrair os pontos para ele.

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

Eu adicionei alguns valores escalares ao ponto para que cada um se comporte um pouco diferente na simulação para dar um pouco de realismo. Brinque com esses valores para obter uma sensação diferente. Agora, para atrair o método do mouse. É um pouco longo com os comentários.

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

Esse método pode ser um pouco confuso se você não estiver atualizado sobre sua matemática vetorial. Os vetores podem ser muito visuais e podem ajudar se você desenhar alguns rabiscos em um pedaço de papel manchado de café. Em inglês claro, essa função está obtendo a distância entre o mouse e o ponto. É então mover o ponto um pouco mais perto do ponto baseado em quão próximo já está do ponto e da quantidade de tempo decorrido. Ele faz isso descobrindo a distância a ser movida (um número escalar normal) e multiplicando isso pelo vetor normalizado (um vetor com comprimento 1) do ponto apontando para o mouse. Ok, essa última sentença não era necessariamente simples, mas é um começo.