Projecto 1: Linhas Icy
Primeiro, as nossas linhas são brancas, por isso vamos ao nosso CSS e dar à tela um fundo escuro:
canvas {
background: linear-gradient(45deg, #0d1011 0% 20%, #163486);
}
Construtor de Linhas
Agora de volta a canvas.js
. Vamos criar um novo Line
classe:
class Line {
constructor(x, y, offset) {
this.x = x;
this.y = y;
this.offset = offset;
this.radians = 0;
this.velocity = 0.01;
}
Parte da determinação de cada linha:x
e y
posição, temos algumas outras propriedades para tornar nossos visuais mais interessantes:
- Podemos usar a propriedade
offset
para renderizar as linhas a distâncias variáveis umas das outras. - Também vamos usar movimento circular no nosso exemplo. Podemos usar a propriedade
radians
para definir o ângulo de movimento e a propriedadevelocity
para determinar a velocidade do movimento.
Método de desenho de linhas
Próximo, vamos querer desenhar as nossas linhas. Há um pequeno círculo na parte inferior de cada linha, que podemos definir usando a função arc
, e então a linha dispara para o topo da tela:
class Line {
constructor(x, y, offset) {
this.x = x;
this.y = y;
this.offset = offset;
this.radians = 0;
this.velocity = 0.01;
} draw = () => {
c.strokeStyle = 'rgba(255, 255, 255, 0.5)';
c.fillStyle = 'rgba(255, 255, 255, 0.3)'; c.beginPath();
c.arc(this.x, this.y, 1, 0, Math.PI * 2, false);
c.fill();
c.moveTo(this.x, this.y);
c.lineTo(this.x + 300, this.y - 1000);
c.stroke();
c.closePath(); this.update();
} update = () => {
// this is where we control movement and interactivity
}
}
Para testar se está funcionando, você pode criar uma linha de amostra:
const line = new Line(250, 800, 0);
line.draw();
Gerar 100 linhas
Mas queremos linhas para preencher a tela, então vamos precisar de uma maneira de criar um array de 100 linhas. Aqui está uma versão simples:
const lineArray = ;for (let i = 0; i < 100; i++) { const start = { x: -250, y: 800 };
const unit = 25; lineArray.push(
new Line(
start.x + unit * i,
start.y + i * -3,
0.1 + (1 * i)
)
);
};
Para ver estas linhas, vamos precisar de activar os seus métodos de draw
. Como em breve iremos animá-los, o melhor lugar para fazer isso é na função animate
function:
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
lineArray.forEach(line => {
line.draw();
});
};animate();
Agora temos 100 linhas! Mas podemos tornar a nossa posição inicial mais interessante: no código abaixo, a variável random
ajuda a fornecer uma variação sutil que dá ao efeito geral um aspecto mais natural. (Eu também estou usando Math.sin(i)
para adicionar alguma ondulação nas posições iniciais. Isto não é necessário, mas é um toque agradável.)
const lineArray = ;for (let i = 0; i < 100; i++) { const start = { x: -250, y: 800 };
const random = Math.random() - 0.5;
const unit = 25; lineArray.push(
new Line(
start.x + ((unit + random) * i),
start.y + (i + random) * -3 + Math.sin(i) * unit,
0.1 + (1 * i)
)
);
};
Jogue com os números até ter um padrão inicial que você gosta!
Animação
Daqui, é muito simples adicionar a animação que queremos. Volte à nossa classe Line
, e depois adicione o seguinte no método update
:
this.radians += this.velocity;
this.y = this.y + Math.cos(this.radians + this.offset);
Com cada frame de animação, this.radians
irá aumentar em this.velocity
. Usaremos então this.radians
para criar movimento circular, via Math.cos
.
Se você não encontrou Math.sin
ou Math.cos
antes (ou sua trigonometria está um pouco enferrujada!), isso não deve impedi-lo de seguir adiante. Basta saber que Math.cos
nos permite criar um movimento de vaivém, num padrão idêntico à linha roxa do gráfico abaixo:
Por último, adicionando this.offset
, as linhas começarão a fazer looping em ângulos diferentes.
Eu acho que este é um efeito bastante agradável. Mas para um toque final, vamos adicionar alguma interatividade. Quando o usuário passar o mouse sobre as linhas, vamos fazê-las mudar de cor.
Interatividade
A maneira mais simples de ver se o mouse do usuário está sobre uma das linhas é usar o canvas embutido do método.isPointInPath
Mas, sem mais ajustes, isto não vai ser muito útil. As linhas têm apenas 1 pixel de largura, então a chance de acionar qualquer mudança será muito baixa para ser interessante!
Uma grande solução para este problema é criar linhas invisíveis atrás de cada uma de nossas linhas. As linhas invisíveis devem ser mais largas, e podemos usá-las para disparar isPointInPath.
isPointInPath
No nosso método draw
, vamos criar uma função chamada drawLinePath
. Deve ser necessário um width
para nossas linhas invisíveis, e um color
(que nossas linhas visíveis se tornarão, quando as linhas invisíveis estiverem em contato):
const drawLinePath = (width = 0, color) => {
c.beginPath();
c.moveTo(this.x - (width / 2), this.y + (width / 2));
c.lineTo(this.x - (width / 2) + 300, this.y - (width / 2) - 1000);
c.lineTo(this.x + (width / 2) + 300, this.y - (width / 2) - 1000);
c.lineTo(this.x + (width / 2), this.y - (width / 2));
c.closePath();
if (c.isPointInPath(mouse.x, mouse.y) && color) {
c.strokeStyle = color;
};
};
(Note que o uso de funções de seta acima implicitamente vincula o contexto correto para this
).
Podemos então facilmente adicionar diferentes larguras de linhas invisíveis para disparar diferentes mudanças de cor. O seguinte código deve deixar claro o que está acontecendo:
drawLinePath(150, 'red');
drawLinePath(50, 'yellow');
But, para um efeito mais sutil, tente:
drawLinePath(150, '#baf2ef');
drawLinePath(50, '#dcf3ff');
E isso é um wrap!
Para o código JavaScript completo, confira este gist.