Este artigo explora como utilizar a API Canvas do HTML5 para converter dados de imagem em efeitos visuais baseados em partículas, cobrindo desde a leitura de pixels até a renderização animada.
Conceitos Fundamentais
Cada pixel em uma imagem é representado por quatro valores: Vermelho, Verde, Azul e Alfa (RGBA). Para acessar os dados de uma região no Canvas, usa-se o método getImageData(), que retorna um array onde cada grupo de quatro elementos corresponde a um pixel. A posição de um pixel na linha i e coluna j pode ser calculada com (i * largura + j) * 4. Em vez de renderizar os pixels diretamente, podemos deslocar suas posições para criar animações dispersas.
Exemplo 1: Efeito de Montagem de Imagem
Este exemplo carrega uma imagem, extrai seus pixels e os desenha com variações aleatórias de posição para simular um efeito de montagem.
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Efeito de Montagem em Canvas</title>
</head>
<body>
<canvas id="canvasMontagem" width="600" height="400"></canvas>
<script>
const tela = document.getElementById('canvasMontagem');
const contexto = tela.getContext('2d');
const imagem = new Image();
imagem.src = 'caminho/para/imagem.jpg';
imagem.crossOrigin = 'anonymous';
let pontos = [];
imagem.onload = function() {
contexto.drawImage(imagem, 200, 100, 200, 200);
const dadosPixels = contexto.getImageData(200, 100, 200, 200);
extrairPontos(dadosPixels);
desenharPontos();
};
function extrairPontos(dados) {
const valores = dados.data;
const largura = dados.width;
const altura = dados.height;
for (let y = 0; y < altura; y++) {
for (let x = 0; x < largura; x++) {
const indice = (y * largura + x) * 4;
if (valores[indice + 3] > 0) {
const ponto = {
posX: 200 + x + (Math.random() - 0.5) * 30,
posY: 100 + y + (Math.random() - 0.5) * 30,
cor: `rgba(${valores[indice]}, ${valores[indice+1]}, ${valores[indice+2]}, ${valores[indice+3] / 255})`
};
pontos.push(ponto);
}
}
}
}
function desenharPontos() {
contexto.clearRect(0, 0, 600, 400);
pontos.forEach(function(ponto) {
contexto.fillStyle = ponto.cor;
contexto.fillRect(ponto.posX, ponto.posY, 2, 2);
});
}
</script>
</body>
</html>
Exemplo 2: Efeito de Relógio com Partículas
Este exemplo renderiza a hora atual como texto no Canvas, converte o texto em partículas e anima-as continuamente para criar um efeito dinâmico.
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Relógio de Partículas</title>
<style>
body { margin: 0; background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; }
canvas { border: 1px solid #333; }
</style>
</head>
<body>
<canvas id="canvasRelogio" width="800" height="200"></canvas>
<script>
const canvas = document.getElementById('canvasRelogio');
const ctx = canvas.getContext('2d');
const NUM_PARTICULAS = 3000;
const VELOCIDADE = 0.08;
let particulas = [];
let textoAtual = obterHoraFormatada();
function obterHoraFormatada() {
const agora = new Date();
const horas = String(agora.getHours()).padStart(2, '0');
const minutos = String(agora.getMinutes()).padStart(2, '0');
const segundos = String(agora.getSeconds()).padStart(2, '0');
return `${horas}:${minutos}:${segundos}`;
}
class Particula {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.destinoX = 0;
this.destinoY = 0;
this.tamanho = Math.random() * 2 + 1;
this.opacidade = 0;
this.ativa = false;
}
atualizar() {
if (this.ativa) {
this.opacidade = Math.min(this.opacidade + 0.02, 1);
const dx = this.destinoX - this.x;
const dy = this.destinoY - this.y;
const distancia = Math.sqrt(dx * dx + dy * dy);
if (distancia > 1) {
this.x += dx * VELOCIDADE;
this.y += dy * VELOCIDADE;
}
} else {
this.opacidade = Math.max(this.opacidade - 0.01, 0);
this.x += (Math.random() - 0.5) * 2;
this.y += (Math.random() - 0.5) * 2;
}
}
desenhar() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.tamanho, 0, Math.PI * 2);
ctx.fillStyle = `rgba(100, 200, 255, ${this.opacidade})`;
ctx.fill();
}
}
function inicializarParticulas() {
particulas = [];
for (let i = 0; i < NUM_PARTICULAS; i++) {
particulas.push(new Particula());
}
}
function extrairPixelsDoTexto() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 100px Arial';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(textoAtual, canvas.width / 2, canvas.height / 2);
const dados = ctx.getImageData(0, 0, canvas.width, canvas.height);
ctx.clearRect(0, 0, canvas.width, canvas.height);
const pixelsAtivos = [];
for (let y = 0; y < dados.height; y += 2) {
for (let x = 0; x < dados.width; x += 2) {
const idx = (y * dados.width + x) * 4;
if (dados.data[idx + 3] > 128) {
pixelsAtivos.push({ x: x, y: y });
}
}
}
return pixelsAtivos;
}
function animar() {
const pixels = extrairPixelsDoTexto();
const numPixels = pixels.length;
const numParticulasAtivas = Math.min(numPixels, particulas.length);
for (let i = 0; i < numParticulasAtivas; i++) {
const particula = particulas[i];
particula.destinoX = pixels[i].x;
particula.destinoY = pixels[i].y;
particula.ativa = true;
}
for (let i = numParticulasAtivas; i < particulas.length; i++) {
particulas[i].ativa = false;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
particulas.forEach(function(p) {
p.atualizar();
p.desenhar();
});
requestAnimationFrame(animar);
}
inicializarParticulas();
setInterval(function() {
textoAtual = obterHoraFormatada();
}, 1000);
animar();
</script>
</body>
</html>
Para criar efeitos de partículas, é crucial utilizar getImageData() para obter dados de pixels e redesenhá-los com deslocamentos ou transformações. A limpeza do Canvas com clearRect() antes de cada quadro permite atualizações suaves, enquanto o método requestAnimationFrame() garante animações eficientes.