Introdução

Fala galera, olha eu aqui de novo no último post dessa série que vai abrir seus horizontes para várias coisas que podemos adicionar nos sites que desenvolvemos.

Se voce não viu nem o primeiro nem o segundo post dessa série, indico fortemente voce deixar esse post nessa aba e ir dar uma lida neles, se não o que vai acontecer aqui pode ficar muito confuso.

Nesse post iremos utilizar todo o conhecimento obtido nos outros posts e fazer um efeito que ja vai estar ready to implement no seu site. :)

Aquele início de sempre

Se voces não lembravam onde tudo começa, a primeira coisa que devemos definir é a nossa tag canvas e os estilos para deixar nosso canvas ocupando a tela inteira. Dessa vez irei deixar o background da página sendo o fundo do nosso canvas e nele iremos desenhar somente as partículas !

index.html

<style>
   *{padding:0;margin:0;}
   #mycanvas{position:fixed;}
   body{background-color: #0a0014;}
</style>
...
<canvas id="mycanvas" width="150" height="150"></canvas>
...
	
index.js

window.onload = draw;

function draw(){
   var canvas = document.getElementById("mycanvas");
   // IMPORTANTE !
   // lembrem que o nosso canvas não aceita width/height 100% via css
   // sendo assim temos que definir pelo JS
   canvas.width = window.innerWidth;
   canvas.height = window.innerHeight;
   
   var context = canvas.getContext("2d");
}
	

As partículas

A primeira coisa a se fazer é a programação das nossas particulas, definir aceleração, as funções de desenho, movimento etc.

index.js

window.onload = draw;
function draw(){ ... }

/* criamos o nosso function constructor,
   passando como parametros:
   ctx = context do nosso elemento canvas
   x = posição inicial X da particula
   y = posição inicial Y da particula
   vX = aceleração das particulas em X
   vY = aceleração das particulas em Y */
function Particle(ctx, x, y, vX, vY){
   // dentro do FC nós declaramos o tamanho 
   // default da nossa particula como 1
   this.radius = 1;
   
   // e adicionamos todos os argumentos
   // no nosso objeto particula
   this.ctx = ctx;
   this.x = x;
   this.y = y;
   this.vX = vX;
   this.vY = vY;

   // por fim desenhamos ela a primeira vez na tela, na posição inicial
   this.draw();
}

/* agora definimos as funções de desenho 
   e movimentação no prototype da nossa particula */

// se voce não entende sobre prototype ainda, não se preocupe
// só tenha em mente que essas funções serão acessiveis pelas nossas particulas
Particle.prototype.draw = function(){
   /* quando vamos fazer qualquer desenho,
      devemos sempre salvar o estado do nosso palco
      e restaurá-lo no final do nosso desenho */
   this.ctx.save();
   
   /* aqui definimos o radius em uma variável para facilitar seu acesso */
   var radius = this.radius;
   
   /* fazemos todos aqueles passos, começar o caminho, desenhar, fechar e completar !*/
   this.ctx.beginPath();
   this.ctx.arc(this.x, this.y, radius, 0, Math.PI*2);
   this.ctx.closePath();
   this.ctx.fillStyle = "#fff"; // a cor da nossa partícula será branca
   this.ctx.fill();
   
   /* restauramos o nosso palco ao estado original, 
      nenhuma alteração de cor, posição da caneta do
      desenho, etc.. será salva */
   this.ctx.restore();
}
Particle.prototype.move = function(){
   /* na nossa função que controla a movimentação 
      nós iremos somar a velocidade da nossa particula
      na posição atual dela, em x e y */
   this.x+=this.vX;
   this.y+=this.vY;
   
   /* após mudar a posição da partícula, "movimenta-la",
      nós podemos*/
   this.draw();
}

O código da nossa partícula está pronto !

O container das partículas

Essas partículas terão que ser administradas por outro container, e esse será responsável por movimentar todas em conjunto, criá-las, e manter o controle da quantidade máxima que estará vagando pela tela.

index.js

window.onload = draw;
function draw(){
   var canvas = document.getElementById("mycanvas");
   canvas.width = window.innerWidth;
   canvas.height = window.innerHeight;

   /* como iriamos ter que passar o contexto,
      a largura do nosso elemento canvas 
      e a altura do nosso elemento 
      mudamos o código para passar o elemento e 
      dentro da função de inicialização do nosso 
      container, nós obtemos a largura/altura e 
      contexto de desenho */   
   particles.init(canvas);
}

/* criação de um objeto normal */
var particles = {
   /* valores default de algumas variáveis*/
   canvasEl:null,
   width:0,
   height:0,
   /* array que guardará nossas partículas */
   particles:[],
   /* definição da quantidade máxima de particulas */
   maxParticles:150,
   /* nossa função de inicialização responsável por 
      definir os valores das nossas variáveis */
   init:function(canvasEl){
      /* canvasEl é o próprio */
      this.canvasEl = canvasEl;
      /* o width do nosso container é igual ao width do nosso elemento */
      this.width = this.canvasEl.width;
      /* o height do nosso container é igual ao height do nosso elemento */
      this.height = this.canvasEl.height;
      /* obtenção do nosso contexto de renderização */
      this.ctx = this.canvasEl.getContext("2d");
      
      /* depois de setar todas as variáveis, nós mandamos 
         montar o array que guardará as nossas partículas */
      this.mountParticles();
   },
   /* função responsável por montar o array das partículas */
   mountParticles:function(){
      /* na função nós fazemos um loop que criará o número de partículas
         máximo nas posições do nosso array de partículas */
      for(var i = 0; i < this.maxParticles; i++){
         /* para dar um pouco mais de dinamismo às nossas partículas 
            deixaremos todas as variáveis serem definidas aleatóriamente */
         var initialX = Math.random()*this.width; // a posição inicial será algum ponto
         var initialY = Math.random()*this.height;// dentro dos limites do nosso container

         /* as velocidades máximas em x e Y será 2 pixels por frame
            e a direção será definida aleatoriamente por (-1)^numero_aleatorio
            assim a direção poderá ser negativa ou positiva e teremos partículas
            viajando por todos os lados */
         var speedX = 2*Math.random()*Math.pow(-1, Math.floor(Math.random()*10));
         var speedY = 2*Math.random()*Math.pow(-1, Math.floor(Math.random()*3));

         /* "instanciamos" nossa partículas novas passando 
            todos os valores que obtivemos acima como default */
         this.particles[i] = new Particle(this.ctx, initialX, initialY, speedX, speedY);
      }

      /* após criar todas as partículas no nosso array,
         finalmente damos o pontapé inicial no movimento 
         das partículas */
      this.moveParticles();
   },
   moveParticles:function(){
      /* como essa função irá redesenhar todas as partículas
         nós temos que limpar o canvas inteiro, dessa forma não
         irá sobrar nenhum rastro delas pela tela */
      this.ctx.clearRect(0, 0, this.width, this.height);
      // indico tirar uma vez para voce entender o que acontece
      // PS: até que fica legal hahaha

      // interagimos em todas as nossas partículas
      this.particles.forEach(function(p){
         /* e mandamos movimentar uma a uma
            dentro da função move da partícula 
            ela chama a função de desenhar, lembram ? */
         p.move();
      });

      /* usando recursividade nós mandamos mandamos requisitamos
         ao navegador que ele mova todas as partículas novamente
         no próximo frame, se tudo estiver rodando ok e leve
         tende a manter a média de 60 frames por segundo
         mas isso é variável de acordo com a complexidade da
         sua animação */
      this.raf = window.requestAnimationFrame(this.moveParticles.bind(this));
      /* IMPORTANTE passar esse objeto com o bind, se não
         a animação não irá ocorrer pois o valor de this
         dentro da função será diferente */
   }
}

// o resto continua igual 
function Particle(ctx, x, y, vX, vY){ ... }
Particle.prototype.draw = function(){ ... }
Particle.prototype.move = function(){ ... }

Se voce seguiu os passos até aqui, voce ja tem uma visualização bem legal na sua tela !

See the Pen Efeito de partículas(incompleto) by Matheus Mesquita (@mathmesquita) on CodePen.

Só temos um problema, nossas partículas estão sumindo quando passam da borda da tela. :(

Mantendo as partículas dentro da tela

O efeito até ta maneirinho, ta tudo se movendo loucamente pra lados aleatórios, mas depois de alguns segundos, ou minutos pras particulas que deram a sorte de pegarem velocidade quase zero, todas elas somem.

Sendo assim o usuário só conseguiria vê-las no primeiro load da página, não poderia ficar admirando toda a beleza daquele movimento por mais tempo. Tendo esse problema, vamos fazer as modificações no nosso código para mantê-ls na tela.

index.js

window.onload = draw;
function draw(){ ... }

var particles = { 
   ...
   mountParticles:function(){
      for(var i = 0; i < this.maxParticles; i++){
         var initialX = Math.random()*this.width;
         var initialY = Math.random()*this.height;
         var speedX = Math.random()*2*Math.pow(-1, Math.floor(Math.random()*10));
         var speedY = Math.random()*2*Math.pow(-1, Math.floor(Math.random()*10));

         // this.particles[i] = new Particle(this.ctx, initialX, initialY, speedX, speedY);
         /* não precisamos mais passar o context para nossas partículas 
            pois ele será acessível pela cadeia do prototype */
         this.particles[i] = new Particle(initialX, initialY, speedX, speedY);
      }
      this.moveParticles();
   },
   ...
}


function Particle(x, y, vX, vY){ 
   this.radius = 1;
   
   // this.ctx = ctx;
   // não precisamos mais definir o contexto dentro da partícula
   this.x = x;
   this.y = y;
   this.vX = vX;
   this.vY = vY;

   this.draw();
}
/* aqui definimos que o prototype da nossa particula 
   vai ser o nosso container de partículas
   isso nos permitirá ter acesso as propriedades do container
   diretamente pela partícula com o uso do this */
Particle.prototype = particles;
/* essa declaração tem que estar acima das declarações abaixo
   pois será como se estivessemos adicionando mais métodos no 
   nosso prototype, de declararmos abaixo das funções, nós 
   iriamos sobrescreve-las e tudo pararia de funcionar */

Particle.prototype.draw = function(){ ... } // função continua igual

Particle.prototype.move = function(){
   this.x+=this.vX;
   /* precisamos adicionar esses dois ifs
      que irão verificar se a partícula saiu
      da tela e reiniciar a posição para o lado
      oposto, parecendo que ela entrou em um 
      portal em cima e apareceu em baixo
      ou vice-versa */
   if(this.x > this.width) {this.x = 0;}
   if(this.x < 0) {this.x = this.width;}

   this.y+=this.vY;
   /* mesma coisa da esquerda para direita
      ou vice-versa */
   if(this.y > this.height) {this.y = 0;}
   if(this.y < 0) {this.y = this.height;}

   /* NOTEM QUE as propriedades this.width e this.height que 
      estamos consultando, estão sendo herdadas do
      objeto de particles graças ao prototype ! */
   /* lembrando que essas propriedades representam a largura
      e altura do nosso elemento canvas */
   
   this.draw();
}

Feitas as alterações acima, temos o nosso efeito de partículas finalizado !!! uhul !!!

See the Pen Efeito de partículas(incompleto) 2 by Matheus Mesquita (@mathmesquita) on CodePen.

Só isso matheus?

Para aqueles que acharam que ficou legal mas beehhh..., vamos fazer umas modificações e botar uma interação com o mouse na tela ! Assim o usuário irá poder procrastinar no seu site durante horas interagindo com as partículas.

index.js

window.onload = draw;

function draw(){
   var canvas = document.getElementById("mycanvas");
   canvas.width = window.innerWidth;
   canvas.height = window.innerHeight;

   particles.init(canvas);
   
   /* vamos botar um evento de quando o mouse se move */
   canvas.onmousemove = particles.mouseHandler.bind(particles);

   /* e quando ele sair da tela as posições serão "resetadas"
      dessa forma não teremos nenhum bug de linha levando a nada */
   canvas.onmouseleave = particles.mouseHandler.bind(particles, {clientX:-100, clientY:-100});

   /* LEMBRANDO QUE, é importante passar esses métodos 
      com o bind apontando para o objeto particles 
      se não a keyword this não terá a mesma funcionalidade
      dentro da função */
}

var particles = {
   ...
   /* definimos as posições default do mouse em X e Y
      essas posições equivalem a como se ele estivesse
      fora da tela */
   mouseX: -100,
   mouseY: -100,
   ...
   /* todas as outras funções continuam iguais
      só precisaremo adicionar o nosso handler do mouse
      para mudarmos a posição do mouse no nosso objeto */
   mouseHandler:function(e){
      this.mouseX = e.clientX;
      this.mouseY = e.clientY;
   }
   ...
}

function Particle(x, y, vX, vY){ ... }
Particle.prototype = particles;
/* o resto continua igual e precisaremos 
   realizar modificações na função que desenha
   as nossas partículas */
Particle.prototype.draw = function(){
   this.ctx.save();

   var radius = this.radius;
   /* nós adicionamos a variável que mede a distância para o mouse
      dessa forma quando a partícula estiver próxima ao mouse, 
      mais precisamente a menos de 100px, uma linha ligará o mouse
      a partícula, nota-se que estamos acessando as propriedades
      mouseX e mouseY do objeto particles, graças ao prototype chain */
   var distanceToMouse = Math.sqrt(Math.pow(this.mouseX-this.x, 2) + Math.pow(this.mouseY-this.y, 2));
   // condicional para verificar a proximidade
   if(distanceToMouse < 100){
      // se estiver próximo desenha a linha
      this.drawLine(distanceToMouse);
      // e aumenta o tamanho da nossa bolinha
      radius*=3;
   }
   
   // resto continua igual
   this.ctx.beginPath();
   this.ctx.arc(this.x, this.y, radius, 0, Math.PI*2);
   this.ctx.closePath();
   this.ctx.fillStyle = "#fff";
   this.ctx.fill();

   this.ctx.restore();
}
Particle.prototype.drawLine = function(distanceToMouse){
   /* desenho simples de uma linha, seguindo todos
      os passos para se desenhar um caminho */
   this.ctx.beginPath();
   this.ctx.moveTo(this.x, this.y);
   this.ctx.lineTo(this.mouseX, this.mouseY);
   this.ctx.closePath();

   /* aqui definimos a cor da linha para ficar mais 
      transparente quanto mais longe estiver do mouse */
   this.ctx.strokeStyle = "rgba(255, 255, 255, "+(1-(distanceToMouse/100)).toFixed(1)+")";
   /* mandamos desenhar a linha */
   this.ctx.stroke();
}
Particle.prototype.move = function(){ ... } // continua igual

Resultado

See the Pen Efeito de partículas by Matheus Mesquita (@mathmesquita) on CodePen.

Conclusão

É isso aí galera, espero que tenham gostado do efeito, e dessa série, para quem quiser mais posts sobre canvas é só pedir nos comentários, e pra quem se interessou por tudo que fizemos com o javascript é só pedir posts sobre javascript tb nos comentários !

O efeito é simples mas é de coração, existem libraries como o particles.js que ja tem alguns efeitos bem legais prontos também, lembrando que agora voces ja sabem o caminho das pedras para desenvolverem os próprios efeitos.

Qualquer dúvida, sugestão, critíca, desabafo etc estarei nos comentários ! para quem ficou esperando post meu ontem, peço desculpas pelo meu notebook, meu windows corrompeu e basicamente ficarei 1 mês sem ele :( , sendo assim também não terá post amanhã, mas durante a semana que vem irão rolar alguns posts sim !

Abraço pessoal e até a próxima ! obrigado a quem leu até aqui e nos vemos no próximo post !!