Pular para o conteúdo

HTML5+JS

Os efeitos são criados usando JavaScript puro num elemento canvas do HTML5. Nesta secçã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 a 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 estes 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, portanto 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 estes passos, a metade interior do contorno seria coberta pelo preenchimento vermelho.

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

Os arcos são desenhados rodando 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. Ao contrário 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 pode ver, os arcos são desenhados no sentido horário por predefinição. Isto 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 levar vinte linhas de código, portanto, se precisar de desenhar cem formas, precisamos de uma abordagem mais eficiente. Os 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 fila 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 grelha xadrez. Vou alterar ligeiramente 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'
var iRow = Math.floor(i / 4);
var iCol = i % 4;
var ix = iCol * 20;
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.
if(iRow % 2 == 0){
if(i % 2 == 0){
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
} else {
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
}
} else {
if(i % 2 == 0){
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
} else {
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 se sentir inseguro com trigonometria ou geometria, recomendo fazer uma pesquisa básica para levar as suas competências ao próximo nível. Nos próximos exemplos, vou pegar num círculo estático e associar cada variável possível a um movimento oscilatório simples.

O padrão de ouro para oscilação (movimento regular de vai e vem) é calcular o sin() ou cosseno() de um temporizador. Qualquer uma das operações retornará um valor entre -1 e 1 para QUALQUER valor passado, mesmo que esteja apenas a contar. A única diferença entre as duas é que estão ligeiramente fora de sincronia — quando cos é 0, sin é -1 ou 1, e vice-versa. Podemos usar isto 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 agora para manter a forma simples, pois o resto será bastante 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 criámos 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, consulte a nossa página de Callbacks!