Web Workers FTW!

Como todos sabemos, paralelismo sempre foi um problema quando estamos falando de JavaScript. Porém, com a Web Workers API a situação muda. Vamos conhecer essa oitava maravilha do mundo!

Imagine a seguinte situação: uma página onde você carrega um plugin de slideshow, autenticação com Facebook e load de dados através de JavaScript, entre outra carralhada de arquivos. Comum, certo? Agora me diga, quantas vezes você teve problema com botões que não respondiam aos seus eventos? Nenhum? Ok. Quantas vezes essa aplicação gerou um crash no navegador?

Se você já enfrentou os problemas que citei acima, keep calm, você não está 100% errado. Cada vez mais, nós desenvolvedores, estamos fazendo aplicações que exigem trabalho pesado usando JavaScript. Pensando nisso, eis que surge a oitava maravilha do mundo, Web Workers API, que usa mais de um processo para executar tarefas JavaScript paralelamente.

O que é paralelismo

Vamos à uma rápida explicação. Caso você já esteja ligado no assunto, pule para o próximo tópico.

Paralelismo, ou melhor, Programação Paralela, é a forma com que conseguimos fazer um algoritmo executar diversas tarefas usando mais de um processo, thread, ou no nosso caso, workers! Tudo isso com o intuito de ganhar mais performance. Não entendeu? Beleza. Uma imagem fala mais do que mil palavras:

Web Workers, como funciona

Se interessou por Programação Paralela? Ainda não entendeu? Aí vai uma boa referência: https://computing.llnl.gov/tutorials/parallel_comp/.

Como funciona

Atualmente é possível executar eventos de forma assíncrona usando setTimeout(), setInterval(), XMLHttpRequest entre outros hacks (ok, chutem-me). Porém, isso não quer dizer que é paralelo, sendo que os eventos são executados quando o script é renderizado.

Web Workers trabalham de forma diferente. E melhor. Como JavaScript não é uma linguagem que usa multi-threads, os Workers de certa forma “estendem-a” em alguns casos, possibilitando executar tarefas com mais uma thread. A API define que processos serão executados de forma assíncrona em outro contexto, por scripts que se comunicam através de mensagens que são passadas pelo método postMessage().

Mãos à obra

Para não sair da tradição (risos), vamos à um “Hello World”.

Como Web Workers executam paralelamente de forma isolada ao processo principal, vamos criar um novo arquivo que receberá a “ordem” desse processo e executará sua função. No nosso exemplo, chamei de worker.js e possui a função abaixo:

// recebe a mensagem do processo principal
onmessage = function(event) {
  var message = event.data;
  var result = message + ' no And After!';
  postMessage(result);
}

Feito isso, vamos criá-lo no processo principal:

var myWorker = new Worker('worker.js');

Para passar nossa informação, precisamos do método postMessage():

var message = 'Web Workers';
myWorker.postMessage(message);

E para finalizar, precisamos receber a mensagem do Worker:

// Recebe a mensagem do worker
myWorker.onmessage = function(event) {
  alert(event.data);
};

Se não quiser fazer na mão, é só baixar o exemplo aqui. Ou, você pode ver uns exemplos nesse link.

PS: isso não vai rodar localmente por restrições dos browsers modernos. Então, para aproveitar a trégua, aproveito para explicar que, como workers são arquivos externos, não será possível carregá-lo através de scripts que começaram com http:. Para ver esse exemplo funcionando, clique aqui.

Leia mais sobre segurança em http://www.html5rocks.com/en/tutorials/workers/basics/#toc-security

Talvez você esteja pensando “porr*, pra quê tanta enrolação para escrever uma mensagem na tela?”. Calma lá. Web Workers servem para auxiliar em tarefas porradas, como renderização 3D, cálculos que exigem mais desempenho, etc.

Importante: caso você não tenha percebido, o nosso exemplo não acessa o DOM diretamente. Web Workers não tem acesso ao DOM!

Além do DOM, Web Workers não tem acesso aos objetos window, document e parent. Mas calma, apesar disso, Web Workers tem acesso aos seguintes recursos do JavaScript:

  • navigator com quatro propriedades: appName, appVersion, userAgent e platform.
  • location (somente leitura)
  • self, que aponta para o objeto worker global
  • importScripts() para fazer load de um JavaScript externo
  • todos os objetos do JavaScript, como Object, Array, Math, etc
  • XMLHttpRequest
  • setTimeout()/clearTimeout() e setInterval()/clearInterval()
  • há também um método close() usado parar um Worker

Conclusão

Web Workers é algo novo, logo, a API pode mudar a qualquer hora, e pra melhor. Acredito que eles irão além do browser. Isso é só o começo, e já é demais!

Referências:

App nativa ou web app?

Uma das primeiras decisões que um time tem que passar no desenvolvimento de aplicativos mobile é se irão desenvolver um aplicativo nativo ou se irão desenvolver um aplicativo web para manter apenas uma versão para diversos dispositivos.

É uma decisão que pode ser bem complexa e a resposta mais correta depende muito das necessidades do seu aplicativo, do seu público e do time disponível. A apresentação abaixo mostra alguns pontos interessantes que podem influenciar na decisão.

[slideshare id=16148972&doc=appceleratorhtml5prezfinal-130123232125-phpapp02]

E como sempre, vale lembrar que não existe “bala de prata”.

Pong em HTML5 Canvas

Recentemente eu escrevi um turorial de Canvas (HTML5) para iniciantes, que comecei a escrever para aprender Canvas.

Comecei a buscar formas divertidas de aprender canvas e como games em HTML5 me chamaram a atenção desde a primeira vez que li sobre o assunto, porque não aprender canvas desenvolvendo algum game? 🙂

É também uma forma de mostrar o que é possível com HTML5 e expandir as idéias para as aplicações que podem ser feitas com um pouco de criatividade.

Pong em Canvas

Vou explicar o desenvolvimento do Pong em HTML5.

Para facilitar a manutenção procurei desenvolver todo o game com orientação a objetos em quase tudo, então recomendo leitura sobre o assunto caso você não se sinta confortável com esse tipo de código.

O primeiro passo é criar a estrutura do game:

function Pong(){
    _this = this;
}

Todo o game é estruturado em basicamente 2 objetos de dados e algumas funções (métodos) auxiliares que fazem a manipulação dos dados, animação e regras do jogo.

 

Os objetos do game

Players

Cada player é um objeto onde é armazenado as informações do jogador, como pontuação, id, localização do seu "pad" e regra de movimentação (se ele deve ir para a direita ou esquerda).

this.player = function(){

    this.score = 0,
    this.left = false,
    this.right = false;    

    this.paddle = {
        x : 0,
        y : (_this.HEIGHT / 2)-60,
        w : 7,
        h : 120
    };

}

 

 

Bola

A bola é outro objeto do game, nela ficam armazenados sua posição (x,y) e o raio utilizado para o desenho.

this.bola = {
      x : 8,
      y : 50,
      r : 6
}

 

Métodos do Pong

Estruturei uma série de funções para facilitar o desenho e aplicação das regras do game. Acredito que o código ficou bastante legível e você pode ler ele diretamente no game.

 

init()

Chamada no início do jogo esta função define o contexto do canvas, os valores iniciais dos objetos (players, bola) e armazena também o tamanho do canvas, é utilizado para posicionar os pads e a bola. A função também cria os 2 jogadores e posiciona seus "pads" no local correto.

As variáveis "wind" e "gravity" são as duas forças que atuam na bola (horizontal e verticalmente).

 

                this.init = function(selector){

                    _this.ctx = $("#canvas")[0].getContext("2d"),

                    _this.WIDTH = $("#canvas")[0].width,

                    _this.HEIGHT = $("#canvas")[0].height,

                    _this.gravity = 4,

                    _this.wind = 10,

                    _this.p1 = new _this.player(),

                    _this.p1.id = 1,

                    _this.p2 = new _this.player(),

                    _this.p2.id = 2,

                    _this.p2.paddle.x = (_this.WIDTH-7);

                    

                    $(document).keydown(_this.onKeyDown);

                    $(document).keyup(_this.onKeyUp);

                    

                }

 

 

 
_this.ctx = $("#canvas")[0].getContext("2d"),
 
_this.WIDTH = $("#canvas")[0].width,
 
_this.HEIGHT = $("#canvas")[0].height,
 
_this.gravity = 4,
 
_this.wind = 10,
 
_this.p1 = new _this.player(),
 
_this.p1.id = 1,
 
_this.p2 = new _this.player(),
 
_this.p2.id = 2,
 
_this.p2.paddle.x = (_this.WIDTH-7);
 
 
 
$(document).keydown(_this.onKeyDown);
 
$(document).keyup(_this.onKe

Funções de desenho

Os métodos circle, rect e clean são apenas atalhos para os métodos de desenho do Canvas, utilizados para a cada frame redesenhar a bola e os pads em suas novas posições.

 

                this.circle = function(x,y,r){

                    _this.ctx.fillStyle = "rgba(0,0,255,.9)";

                    _this.ctx.beginPath();

                    _this.ctx.arc(x, y, r, 0, Math.PI*2, true);

                    _this.ctx.closePath();

                    _this.ctx.fill();

                }

                

                this.rect = function(x,y,w,h){

                    _this.ctx.fillStyle = "rgba(255,0,0,.6)";

                    _this.ctx.beginPath();

                    _this.ctx.rect(x,y,w,h);

                    _this.ctx.closePath();

                    _this.ctx.fill();

                }

                

                this.clear = function(){

                    _this.ctx.clearRect(0, 0, _this.WIDTH,_this.HEIGHT);

                }

 

 

Interação com teclado

Como fazer um objeto se movimentar no canvas utilizando o teclado? Na função init do game defini que a ação "keyup" e "keydown" do jQuery iriam disparar um método do game. 

A cada ação de Keydown é feito uma verificação de qual tecla foi pressionada. Se a tecla pressionada foi a direcional esquerda ou a direcional direita (veja a tabela de keycodes para javascript) o objeto do player 1 é alterado, modificando a flag "left" ou a flag "right" para true.

O Keydown faz a ação inversa, tornando as flags "false" dependendo de qual tecla foi liberada do teclado.

 

 

_this.onKeyDown = function(evt) {

    switch(evt.keyCode){

        /* seta direita */

        case 39:

            _this.p1.right = true;

            break;

        /* seta esquerda */

        case 37:

            _this.p1.left = true;

            break;

        /* espaco */

        case 32:

            _this.play();

            break;

    }

  

}

 

/* Keyup events */

_this.onKeyUp = function(evt) {

    switch(evt.keyCode){

        /* seta direita */

        case 39:

            _this.p1.right = false;

            break;

        /* seta esquerda */

        case 37:

            _this.p1.left = false;

            break;

    }

}

 

 

 

As regras e animação do jogo

Neste ponto nós já temos os objetos e players, bola, e todas as informações do canvas e forças que vão atuar no nosso jogo. Também temos os controles configurados, ao pressionar as teclas nosso objeto player é alterado.

Agora precisamos "dar vida" ao game, para isso iremos redesenhar o nosso canvas a cada 25 milisegundos. Primeiro vamos a lógica:

A cada 25 milisegundos iremos:

  1. Calcular e desenhar a bola em sua nova posição (de acordo com os valores de wind e gravity, que são as forças que atuam na bolinha)
  2. Verificar se o pad do player 1 deve ir para cima ou para baixo (variáveis left e right indicando que as teclas estão pressionadas)

    1. Em caso positivo, desenhar o pad na nova posição
  3. Se a bolinha bater em cima ou em baixo do canvas, inverter o valor de "gravity" para que a bolinha continue dentro do nosso canvas
  4. Verificar se é gol (se a bola acertou a lateral esquerda ou direita do canvas sem atingir os pads)

 

Estas são as regras do game convertidas para "bom português", agora vamos para o código:

 

this.animate = function (){

    /* limpa e redesenha a bola */

    _this.clear();

    _this.circle(_this.bola.x,_this.bola.y,_this.bola.r);

    

    var speed = 8;

    

    /* player 1 */

    if (_this.p1.right && _this.p1.paddle.y < (_this.HEIGHT – _this.p1.paddle.h)) _this.p1.paddle.y += speed;

    else if (_this.p1.left && _this.p1.paddle.y > 0) _this.p1.paddle.y -= speed;

    _this.rect(_this.p1.paddle.x,_this.p1.paddle.y,_this.p1.paddle.w,_this.p1.paddle.h);

    

    /* saida lateral */

    if (_this.bola.y + _this.gravity > _this.HEIGHT || _this.bola.y + _this.gravity < 0){

        _this.gravity = -_this.gravity;

    }

    

    if(_this.bola.x + _this.wind> _this.WIDTH){

        if(_this.bola.y > _this.p2.paddle.y && _this.bola.y < (_this.p2.paddle.y + _this.p2.paddle.h)){

           _this.wind = -_this.wind;

           _this.gravity = _this.randomize();

        }else{

           _this.p1.score++;

           $('#score-1').html(_this.p1.score);

            _this.bola.x = (_this.WIDTH / 2),

            _this.bola.y = (_this.HEIGHT / 2);

            _this.wind = -_this.wind;

           _this.play();

        }

    }else if(_this.bola.x + _this.wind < 0){

        if(_this.bola.y > _this.p1.paddle.y && _this.bola.y < (_this.p1.paddle.y + _this.p1.paddle.h)){

           _this.wind = -_this.wind;

           _this.gravity = _this.randomize();

        }else{

           _this.p2.score++;

           $('#score-2').html(_this.p2.score);

            _this.bola.x = (_this.WIDTH / 2),

            _this.bola.y = (_this.HEIGHT / 2);

            _this.wind = -_this.wind;

           _this.play();

        }

    }

    

    

    _this.bola.y += _this.gravity;

    _this.bola.x += _this.wind;

}

 

O código acima também já está marcando a pontuação e manipulando o HTML para exibição dos pontos. Retirei a parte do player 2 para facilitar, ele é um bot e está explicado abaixo:

 

Player 2 (NPC)

Ainda não trabalhei muito no bot, estava ansioso para compartilhar e colocar o Pong no ar e não consegui deixar exatamente como eu gostaria. Ele "persegue" a bolinha a partir de certo ponto da nossa quadra (do elemento canvas).

 

    /* bot */

    var delay = 300;

    if(_this.bola.y < (_this.p2.paddle.y + (_this.p2.paddle.h / 3)) & _this.p2.paddle.y > 0 && _this.bola.x > delay){

        _this.p2.paddle.y -= speed;

    }else if(_this.bola.y > (_this.p2.paddle.y – (_this.p2.paddle.h / 3)) && _this.p2.paddle.y < (_this.HEIGHT – _this.p2.paddle.h) && _this.bola.x > delay){

        _this.p2.paddle.y += speed;

    }

    _this.rect(_this.p2.paddle.x,_this.p2.paddle.y,_this.p2.paddle.w,_this.p2.paddle.h);

 

 

Espero que este post desperte curiosidade e idéias! Sugestões, críticas e dúvidas nos comentários.

HTML5 Canvas – Tutorial para iniciantes

O que é Canvas HTML5?

 HTML5 trouxe diversas novas tags, além das novidades nas tags input de formulário e das tags semânticas como article e section, uma das grandes mudanças implementadas foi a tag canvas.

Canvas é uma nova tag que permite você trabalhar e manipular elementos gráficos (raster). A tag canvas é um "board" de desenho no HTML, nele você pode desenhar linhas, elementos, manipular pixel a pixel, carregar e manipular imagens externas (rotacionar, alterar cor, brilho, etc.).

É uma evolução gigante pois permite a manipulação em tempo real do que está sendo impresso como imagem no computador do cliente.

Um exemplo de uso são jogos, gráficos e interfaces ainda mais interativas.

 

Quais navegadores dão suporte ao Canvas?

Praticamente todos os navegadores modernos dão suporte ao Canvas. Confira a tabela abaixo:

 

  IE Firefox Chrome
14 versions back     4.0: Supported
13 versions back     5.0: Supported
12 versions back   2.0: Supported 6.0: Supported
11 versions back   3.0: Supported 7.0: Supported
10 versions back   3.5: Supported 8.0: Supported
9 versions back   3.6: Supported 9.0: Supported
8 versions back   4.0: Supported 10.0: Supported
7 versions back   5.0: Supported 11.0: Supported
6 versions back   6.0: Supported 12.0: Supported
5 versions back   7.0: Supported 13.0: Supported
4 versions back 5.5: Not supported 8.0: Supported 14.0: Supported
3 versions back 6.0: Not supported (but has polyfill available) 9.0: Supported 15.0: Supported
2 versions back 7.0: Not supported (but has polyfill available) 10.0: Supported 16.0: Supported
Previous version 8.0: Not supported (but has polyfill available) 11.0: Supported 17.0: Supported 3.0: Supported
Current 9.0: Supported 12.0: Supported 18.0: Supported
Near future 10.0: Supported 13.0: Supported 19.0: Supported
Farther future   14.0: Supported 20.0: Supported

Veja a tabela completa e atualizada no Can I Use

 

Entendendo melhor o canvas

Neste tutorial inicial vou explicar alguns métodos do canvas e seu funcionamento. O elemento canvas é todo manipulado por javascript, e trabalha com contextos. Pelo javascript você terá acesso a diversos métodos para desenhar nesta tela, no contexto 2D você utiliza um plano cartesiano (x e y) para definir a posição de onde vai trabalhar, e através de métodos específicos pode traçar formas, linhas e definir tamanhos e cores utilizadas.

Para fazer uma animação por exemplo, você faz o seu primeiro desenho e através do javascript define um time que irá limpar o seu contexto e redesenhar os objetos em sua nova posição. Isso também server para jogos em HTML5 ou interação com o usuário: quando acontecer a interaçãovocê limpa o contexto e redesenha sua cena com os objetos manipulados em suas novas posições.

 

Vamos a prática!

Programando para Canvas

Coloque o elemento canvas no seu HTML e use um identificador para facilitar a manipulação do javascript.

<canvas id="myCanvas"></canvas>

 

Vamos agora selecionar o nosso canvas e definir o contexto em que iremos trabalhar, aqui já entra o javascript:

window.onload = function() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
};

 

Isso defina que vamos trabalhar com o contexto 2D do canvas.

 

Desenhando uma linha com Canvas

Agora que você já tem o seu contexto definido, vamos desenhar uma linha no nosso gráfico, no javascript:

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(100, 150); //define o ponto inicial do desenho
context.lineTo(450, 50); //define a posição de in
context.lineWidth = 5; // define a largura da linha
context.strokeStyle = "#ff0000"; //define a cor da linha
context.stroke();

 

Pronto, temos uma linha que começa no ponto (100,150) e vai até (450,50).

Desenhando um retângulo com canvas

Agora vamos desenhar um retângulo em nosso canvas, para isso no javascript temos o método rect, que funciona da seguinte forma:

context.rect(x, y, width, height);

O exemplo do código completo:

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(188, 50, 200, 100); // desenha o retângulo
context.fillStyle = '#8ED6FF'; // define o preenchimento do retângulo
context.fill(); // Preenche o retângulo
context.lineWidth = 5; // define a largura da linha do contorno
context.strokeStyle = 'black'; // define a cor do contorno
context.stroke(); // desenha o contorno

 

Desenhando um círculo com canvas

Para desenhar círculos (e arcos) temos o método arc, que funciona da seguinte forma:

context.arc(x, y, radius, startAngle, endAngle, antiClockwise);

No exemplo acima podemos desenhar arco com qualquer angulo de início e fim, para desenhar círculos completos podemos usar o seguinte:

context.arc(x, y, radius, 0 , 2 * Math.PI, false);

Fica mais fácil e temos que nos preocupar apenas com o ponto cartesiano (x,y) e com o tamanho do raio do círculo. Abaixo um exemplo completo:

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var centerX = canvas.width / 2; // pega o centro do canvas (x)
var centerY = canvas.height / 2;  // pega o centro do canvas (y)
var radius = 70; 

context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); // desenha o círculo no centro do canvas com raio = 70
context.fillStyle = "#8ED6FF"; // define a cor de preenchimento
context.fill(); // desenha o preenchimento
context.lineWidth = 5; // define a expessura da borda
context.strokeStyle = "black"; // define a cor da borda
context.stroke(); // desenha a borda

 

Esse é um tutorial introdutório ao Canvas, ele parece mais complicado do que realmente é. Para entender um pouco melhor o funcionamento do canvas e suas animações criei um Pong em HTML5 Canvas, mas ainda não tive tempo de finalizar. O código está com a leitura fácil, vale a pena a leitura. 😉

 

Recomendo a leitura (em inglês) do html5 canvas tutorials, de onde os exemplos deste post foram retirados.

 

Este post te ajudou de alguma forma? Retribua, divulgue o link deste artigo em seu blog, twitter ou facebook e nos ajude a compartilhar conhecimento.