Pular para o conteúdo

HTML5+JS

Os efeitos são criados usando JavaScript puro em um elemento canvas do HTML5. Nesta seção, vamos cobrir o básico: desenhar formas, desenhar com iteração e adicionar movimento.

Para este tutorial, vou usar um template genérico de lightscript:

<head>
<meta description="Template"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
function update() {
// Code goes here
window.requestAnimationFrame(update);
}
window.requestAnimationFrame(update);
</script>

Seja usando uma linha ou várias, o processo é o mesmo.

  1. ctx.beginPath() inicia sua forma
  2. ctx.moveTo(x, y) define o primeiro ponto
  3. ctx.lineTo(x, y) define o próximo ponto e uma linha de volta ao primeiro. Use múltiplos lineTo’s para desenhar formas maiores.
  4. Defina ctx.strokeStyle e/ou ctx.fillStyle (se a forma for fechada)
  5. Use ctx.stroke() para desenhar as linhas e ctx.fill() para preencher formas fechadas. É importante notar que até este ponto, tudo é hipotético — nada é desenhado até que esses comandos sejam executados.

Aqui estão três exemplos usando linhas:

function update() {
// One Line
ctx.beginPath();
ctx.moveTo(30, 30);
ctx.lineTo(30, 170);
ctx.strokeStyle = "blue";
ctx.stroke();
// L
ctx.beginPath();
ctx.moveTo(80, 30);
ctx.lineTo(80, 100);
ctx.lineTo(120, 100);
ctx.strokeStyle = "blue";
ctx.stroke();
// Filled Triangle
ctx.beginPath();
ctx.moveTo(130, 30);
ctx.lineTo(130, 100);
ctx.lineTo(170, 100);
ctx.lineTo(130, 30);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

É importante observar a ordem de desenho aqui: o triângulo preenchido é desenhado por último, então ele cobriria as outras duas formas se estivessem na mesma posição. Além disso, o contorno no triângulo é desenhado após o preenchimento. Se invertêssemos esses passos, a metade interior do contorno seria coberta pelo preenchimento vermelho.

Retângulos são simples e úteis, especialmente para a resolução média de um teclado RGB. O processo é parecido com o de desenhar linhas — basta substituir os comandos moveTo e lineTo por rect(x, y, width, height).

function update() {
// Basic Square
ctx.beginPath();
ctx.rect(30, 30, 75, 75);
ctx.strokeStyle = "blue";
ctx.stroke();
// Filled Rectangle
ctx.beginPath();
ctx.rect(125, 30, 125, 100);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

Arcos são desenhados rotacionando um ponto externo em torno de um ponto central e podem ser usados para criar círculos completos. O comando de arco é ctx.arc(x, y, radius, start angle, end angle), onde ambas as medidas de ângulo são fornecidas em radianos. Diferentemente dos retângulos, um arco é desenhado com a origem (x, y) no centro do arco.

function update() {
// Half-circle
ctx.beginPath();
ctx.arc(70, 100, 30, 0, Math.PI);
ctx.strokeStyle = "blue";
ctx.stroke();
// 3/4 circle
ctx.beginPath();
ctx.arc(140, 100, 30, 0, Math.PI * (3/2));
ctx.strokeStyle = "blue";
ctx.stroke();
// Full circle
ctx.beginPath();
ctx.arc(210, 100, 30, 0, Math.PI * 2);
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
window.requestAnimationFrame(update);
}

Como você pode ver, os arcos são desenhados no sentido horário por padrão. Isso pode ser revertido com um ângulo final menor que o ângulo inicial, ou adicionando “true” como argumento final ao método ctx.arc().

Desenhar três formas à mão pode facilmente tomar vinte linhas de código, então se você precisar desenhar cem formas, precisamos de uma abordagem mais eficiente. Loops em JavaScript são uma forma simples de repetir tarefas e podem ser aplicados ao desenho também.

Aqui está um exemplo usando um for-loop para desenhar uma fileira de quadrados em xadrez:

function update() {
for(let i = 0; i < 8; i++){
// 1. Create rectangle path and draw the stroke for each.
ctx.beginPath();
ctx.rect(i * 20, 20, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 2. If 'i' is even, the fillStyle is black. Otherwise it's white.
if(i % 2 == 0){
ctx.fillStyle = "black";
} else {
ctx.fillStyle = "white";
}
// 3. Fill the shape after setting the fillStyle.
ctx.fill();
}
window.requestAnimationFrame(update);
}

Aqui está um exemplo de while-loop para desenhar uma grade xadrez. Vou alterar levemente a luminosidade dos quadrados pretos e o matiz dos quadrados brancos em cada iteração usando template literals.

function update() {
var i = 0;
while(i < 16){
// 1. X and Y calculations with 'row' and 'column'
// This operation rounds the result down to the nearest integer. For i = (0-3) the row will be 0, for i = (4-7) the row will be 1, and so on
var iRow = Math.floor(i / 4);
// This operation finds the remainder after division, limiting column to the (0-3) range.
var iCol = i % 4;
// Multiply by the square width to find this square's x-origin
var ix = iCol * 20;
// Multiply by the square height to find this square's y-origin
var iy = iRow * 20;
// 2. Path and stroke
ctx.beginPath();
ctx.rect(ix, iy, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 3. If the row is even, every other square is filled black. If odd, switch the black squares.
// Each iteration adds a small amount of the lightness component to the black, and hue to the white.
if(iRow % 2 == 0){
if(i % 2 == 0){
// 'Black'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
} else {
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
}
} else {
if(i % 2 == 0){
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
} else {
// 'Black'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
}
}
ctx.fill();
i++;
}
window.requestAnimationFrame(update);
}

As animações no canvas são limitadas apenas pela sua imaginação e pelo seu conhecimento matemático. Se você se sentir inseguro com trigonometria ou geometria, recomendo fazer uma pesquisa básica para levar suas habilidades ao próximo nível. Nos próximos exemplos, vou pegar um círculo estático e vincular cada variável possível a um movimento oscilatório simples.

O padrão ouro para oscilação (movimento regular de vai e vem) é calcular o sin() ou cosseno() de um timer. Qualquer uma das operações retornará um valor entre -1 e 1 para QUALQUER valor passado, mesmo que esteja apenas contando. A única diferença entre as duas é que elas estão levemente fora de sincronia — quando cos é 0, sin é -1 ou 1, e vice-versa. Podemos usar isso a nosso favor nesta animação —

function update() {
// Find current time in milliseconds, then divide to slow down the animation speed
let time = Date.now() / 100;
// Draw a background so the old shapes don't stick around
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
//USE ONE EXAMPLE AT A TIME
// 1st example - side to side, y is constant
ctx.arc(15 * Math.cos(time) + 160, 100, 30, 0, Math.PI * 2);
// 2nd example - up and down, x is constant
ctx.arc(160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
// 3rd example - circle, x and y are variable
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.fill();
window.requestAnimationFrame(update);
}

Temos mais alguns atributos para alterar ao longo do tempo — raio do círculo, preenchimento e contorno. Vou deixar os ângulos do arco de lado por enquanto para manter a forma simples, pois o resto será bem chamativo.

function update() {
let time = Date.now() / 100;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30 + 15 * Math.sin(time), 0, Math.PI * 2);
ctx.fillStyle = `hsl(${180 + 180 * Math.cos(time)}, 100%, 50%)`;
ctx.fill();
ctx.strokeStyle = `hsl(${180 + 180 * Math.sin(time)}, 100%, 50%)`;
ctx.stroke();
window.requestAnimationFrame(update);
}

Todos os atributos de todas as formas que criamos até agora podem ser alterados ao longo do tempo, e cada animação que faço é uma combinação das habilidades acima. Para mais detalhes sobre como criar animações reutilizáveis e eficientes, confira nossa página de Callbacks!