Neste tutorial, vamos criar uma animação simples para celebrar a chegada do Ano Novo de 2025, utilizando as tecnologias HTML5, CSS3, JavaScript, Anime.js e Canvas API. Vamos criar uma tela cheia com fogos de artifício animados e mensagens de Feliz Ano Novo 2025!
Veja o Feliz Ano Novo 2025 em funcionamento neste link. Feliz Ano Novo 2025.
Veja o vídeo no YouTube:
Pré-requisitos
Antes de começarmos, certifique-se de ter o seguinte instalado em sua máquina:
- Editor de código (Visual Studio Code, Sublime Text, etc.)
- Navegador de internet (Google Chrome, Firefox, etc.)
Não é necessário instalar nenhum pacote adicional, pois utilizaremos links CDN para Anime.js e outras bibliotecas.
Passo 1: Estrutura HTML
Vamos começar criando a estrutura básica do HTML. Crie um arquivo chamado index.html
e adicione o seguinte código:
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Feliz 2025</title>
<link rel="stylesheet" type="text/css" media="screen" href="style.css">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.min.js"></script>
</head>
<body>
<div class="fw-text-wrapper">
<h1 class="fw-text-overlay">
<span class="letters letters-1">Feliz</span>
<span class="letters letters-2">Ano Novo</span>
<span class="letters letters-3">2025</span>
</h1>
</div>
<div id="canvas-container"></div>
<script src="script.js"></script>
</body>
</html>
Explicação do código:
- O
canvas-container
será utilizado para renderizar os fogos de artifício.
- O
fw-text-wrapper
conterá a mensagem "Feliz Ano Novo 2025!" que aparecerá na tela.
Passo 2: Estilo com CSS
Agora, vamos criar um arquivo de estilo chamado style.css
para dar uma aparência bacana ao nosso projeto.
html {
color: #222;
font-size: 1em;
line-height: 1.4;
margin: 0;
padding: 0;
}
body {
background: #171717;
color: #999;
font: 100%/18px helvetica, arial, sans-serif;
}
canvas {
vertical-align: middle;
}
#canvas-container {
height: 900px;
position: absolute;
width: 1600px;
z-index: 2;
}
canvas {
cursor: crosshair;
display: block;
position: relative;
z-index: 3;
}
canvas:active {
cursor: crosshair;
}
.fw-text-wrapper {
position: absolute;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
}
.fw-text-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0.5em;
left: 0;
margin: auto;
height: 1em;
font-weight: 900;
font-size: 4.5em;
}
.fw-text-overlay .letters {
position: absolute;
margin: auto;
left: 0;
top: 0.3em;
right: 0;
opacity: 0;
}
Explicação do código CSS:
- O
canvas
é configurado para ocupar toda a tela.
- A mensagem é centralizada no meio da tela e estilizada com uma fonte grande e em negrito.
Passo 3: Adicionando Animações com JavaScript e Anime.js
Agora, vamos escrever o código JavaScript para criar os fogos de artifício animados. Crie um arquivo chamado script.js
e adicione o seguinte código:
(function($) {
let fwTextOverlay = {};
fwTextOverlay.opacityIn = [0, 1];
fwTextOverlay.scaleIn = [0.2, 1];
fwTextOverlay.scaleOut = 3;
fwTextOverlay.durationIn = 1600;
fwTextOverlay.durationOut = 1600;
fwTextOverlay.delay = 1600;
setTimeout(function() {
anime.timeline({
loop: false
})
.add({
targets: '.fw-text-overlay .letters-1',
opacity: fwTextOverlay
.opacityIn,
scale: fwTextOverlay.scaleIn,
duration: fwTextOverlay
.durationIn
}).add({
targets: '.fw-text-overlay .letters-1',
opacity: 0,
scale: fwTextOverlay.scaleOut,
duration: fwTextOverlay
.durationOut,
easing: "easeInExpo",
delay: fwTextOverlay.delay
}).add({
targets: '.fw-text-overlay .letters-2',
opacity: fwTextOverlay
.opacityIn,
scale: fwTextOverlay.scaleIn,
duration: fwTextOverlay
.durationIn
}).add({
targets: '.fw-text-overlay .letters-2',
opacity: 0,
scale: fwTextOverlay.scaleOut,
duration: fwTextOverlay
.durationOut,
easing: "easeInExpo",
delay: fwTextOverlay.delay
}).add({
targets: '.fw-text-overlay .letters-3',
opacity: fwTextOverlay
.opacityIn,
scale: fwTextOverlay.scaleIn,
duration: fwTextOverlay
.durationIn
}).add({
targets: '.fw-text-overlay .letters-3',
opacity: 0,
scale: fwTextOverlay.scaleOut,
duration: fwTextOverlay
.durationOut,
easing: "easeInExpo",
delay: fwTextOverlay.delay
}).add({
targets: '.fw-text-overlay',
opacity: 0,
duration: 500,
delay: 500
});
}, 1000);
}(jQuery));
(function($) {
let Fireworks;
Fireworks = function() {
let self = this;
let rand = function(rMi, rMa) {
return ~~((Math.random() * (rMa -
rMi + 1)) + rMi);
};
let hitTest = function(x1, y1, w1,
h1, x2, y2, w2, h2) {
return !(x1 + w1 < x2 || x2 +
w2 < x1 || y1 + h1 < y2 || y2 +
h2 < y1);
};
window.requestAnimFrame =
function() {
return window
.requestAnimationFrame || window
.webkitRequestAnimationFrame ||
window
.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window
.msRequestAnimationFrame ||
function(a) {
window.setTimeout(a, 1E3 / 60)
}
}();
self.init = function() {
self.dt = 0;
self.oldTime = Date.now();
self.canvas = document
.createElement('canvas');
self.canvasContainer = $(
'#canvas-container');
let canvasContainerDisabled =
document.getElementById(
'canvas-container');
self.canvas.onselectstart =
function() {
return false;
};
self.canvas.width = self.cw =
1600;
self.canvas.height = self.ch =
900;
self.particles = [];
self.partCount = 500;
self.fireworks = [];
self.mx = self.cw / 2;
self.my = self.ch / 2;
self.currentHue = 67;
self.partSpeed = 6;
self.partSpeedVariance = 11;
self.partWind = 20;
self.partFriction = 10;
self.partGravity = 3;
self.hueMin = 67;
self.hueMax = 83;
self.fworkSpeed = 3;
self.fworkAccel = 3;
self.hueVariance = 58;
self.flickerDensity = 33;
self.showShockwave = true;
self.showTarget = false;
self.clearAlpha = 50;
self.canvasContainer.append(self
.canvas);
self.ctx = self.canvas
.getContext('2d');
self.ctx.lineCap = 'round';
self.ctx.lineJoin = 'round';
self.lineWidth = 1;
self.bindEvents();
self.canvasLoop();
self.canvas.onselectstart =
function() {
return false;
};
};
let Particle = function(x, y,
hue) {
this.x = x;
this.y = y;
this.coordLast = [
{
x: x,
y: y
},
{
x: x,
y: y
},
{
x: x,
y: y
}
];
this.angle = rand(0, 360);
this.speed = rand(((self
.partSpeed - self
.partSpeedVariance) <= 0) ?
1 : self.partSpeed - self
.partSpeedVariance, (self
.partSpeed + self
.partSpeedVariance));
this.friction = 1 - self
.partFriction / 100;
this.gravity = self.partGravity /
2;
this.hue = rand(hue - self
.hueVariance, hue + self
.hueVariance);
this.brightness = rand(50, 80);
this.alpha = rand(40, 100) / 100;
this.decay = rand(10, 50) / 1000;
this.wind = (rand(0, self
.partWind) - (self.partWind /
2)) / 25;
this.lineWidth = self.lineWidth;
};
Particle.prototype.update =
function(index) {
let radians = this.angle * Math
.PI / 180;
let vx = Math.cos(radians) * this
.speed;
let vy = Math.sin(radians) * this
.speed + this.gravity;
this.speed *= this.friction;
this.coordLast[2].x = this
.coordLast[1].x;
this.coordLast[2].y = this
.coordLast[1].y;
this.coordLast[1].x = this
.coordLast[0].x;
this.coordLast[1].y = this
.coordLast[0].y;
this.coordLast[0].x = this.x;
this.coordLast[0].y = this.y;
this.x += vx * self.dt;
this.y += vy * self.dt;
this.angle += this.wind;
this.alpha -= this.decay;
if (!hitTest(0, 0, self.cw, self
.ch, this.x - this.radius, this
.y - this.radius, this.radius *
2, this.radius * 2) || this
.alpha < .05) {
self.particles.splice(index, 1);
}
};
Particle.prototype.draw =
function() {
let coordRand = (rand(1, 3) - 1);
self.ctx.beginPath();
self.ctx.moveTo(Math.round(this
.coordLast[coordRand].x), Math
.round(this.coordLast[
coordRand].y));
self.ctx.lineTo(Math.round(this
.x), Math.round(this.y));
self.ctx.closePath();
self.ctx.strokeStyle = 'hsla(' +
this.hue + ', 100%, ' + this
.brightness + '%, ' + this
.alpha + ')';
self.ctx.stroke();
if (self.flickerDensity > 0) {
let inverseDensity = 50 - self
.flickerDensity;
if (rand(0, inverseDensity) ===
inverseDensity) {
self.ctx.beginPath();
self.ctx.arc(Math.round(this
.x), Math.round(this.y),
rand(this.lineWidth, this
.lineWidth + 3) / 2, 0, Math
.PI * 2, false)
self.ctx.closePath();
let randAlpha = rand(50, 100) /
100;
self.ctx.fillStyle = 'hsla(' +
this.hue + ', 100%, ' + this
.brightness + '%, ' +
randAlpha + ')';
self.ctx.fill();
}
}
};
self.createParticles = function(x,
y, hue) {
let countdown = self.partCount;
while (countdown--) {
self.particles.push(
new Particle(x, y, hue));
}
};
self.updateParticles = function() {
let i = self.particles.length;
while (i--) {
let p = self.particles[i];
p.update(i);
}
};
self.drawParticles = function() {
let i = self.particles.length;
while (i--) {
let p = self.particles[i];
p.draw();
}
};
let Firework = function(startX,
startY, targetX, targetY) {
this.x = startX;
this.y = startY;
this.startX = startX;
this.startY = startY;
this.hitX = false;
this.hitY = false;
this.coordLast = [
{
x: startX,
y: startY
},
{
x: startX,
y: startY
},
{
x: startX,
y: startY
}
];
this.targetX = targetX;
this.targetY = targetY;
this.speed = self.fworkSpeed;
this.angle = Math.atan2(targetY -
startY, targetX - startX);
this.shockwaveAngle = Math.atan2(
targetY - startY, targetX -
startX) + (90 * (Math.PI /
180));
this.acceleration = self
.fworkAccel / 100;
this.hue = self.currentHue;
this.brightness = rand(50, 80);
this.alpha = rand(50, 100) / 100;
this.lineWidth = self.lineWidth;
this.targetRadius = 1;
};
Firework.prototype.update =
function(index) {
self.ctx.lineWidth = this
.lineWidth;
let vx = Math.cos(this.angle) *
this.speed;
let vy = Math.sin(this.angle) *
this.speed;
this.speed *= 1 + this
.acceleration;
this.coordLast[2].x = this
.coordLast[1].x;
this.coordLast[2].y = this
.coordLast[1].y;
this.coordLast[1].x = this
.coordLast[0].x;
this.coordLast[1].y = this
.coordLast[0].y;
this.coordLast[0].x = this.x;
this.coordLast[0].y = this.y;
if (self.showTarget) {
if (this.targetRadius < 8) {
this.targetRadius += .25 * self
.dt;
} else {
this.targetRadius = 1 * self
.dt;
}
}
if (this.startX >= this
.targetX) {
if (this.x + vx <= this
.targetX) {
this.x = this.targetX;
this.hitX = true;
} else {
this.x += vx * self.dt;
}
} else {
if (this.x + vx >= this
.targetX) {
this.x = this.targetX;
this.hitX = true;
} else {
this.x += vx * self.dt;
}
}
if (this.startY >= this
.targetY) {
if (this.y + vy <= this
.targetY) {
this.y = this.targetY;
this.hitY = true;
} else {
this.y += vy * self.dt;
}
} else {
if (this.y + vy >= this
.targetY) {
this.y = this.targetY;
this.hitY = true;
} else {
this.y += vy * self.dt;
}
}
if (this.hitX && this.hitY) {
let randExplosion = rand(0, 9);
self.createParticles(this
.targetX, this.targetY, this
.hue);
self.fireworks.splice(index, 1);
}
};
Firework.prototype.draw =
function() {
self.ctx.lineWidth = this
.lineWidth;
let coordRand = (rand(1, 3) - 1);
self.ctx.beginPath();
self.ctx.moveTo(Math.round(this
.coordLast[coordRand].x), Math
.round(this.coordLast[
coordRand].y));
self.ctx.lineTo(Math.round(this
.x), Math.round(this.y));
self.ctx.closePath();
self.ctx.strokeStyle = 'hsla(' +
this.hue + ', 100%, ' + this
.brightness + '%, ' + this
.alpha + ')';
self.ctx.stroke();
if (self.showTarget) {
self.ctx.save();
self.ctx.beginPath();
self.ctx.arc(Math.round(this
.targetX), Math.round(this
.targetY), this.targetRadius,
0, Math.PI * 2, false)
self.ctx.closePath();
self.ctx.lineWidth = 1;
self.ctx.stroke();
self.ctx.restore();
}
if (self.showShockwave) {
self.ctx.save();
self.ctx.translate(Math.round(
this.x), Math.round(this.y));
self.ctx.rotate(this
.shockwaveAngle);
self.ctx.beginPath();
self.ctx.arc(0, 0, (this.speed /
5), 0, Math.PI, true);
self.ctx.strokeStyle = 'hsla(' +
this.hue + ', 100%, ' + this
.brightness + '%, ' + rand(25,
60) / 100 + ')';
self.ctx.lineWidth = this
.lineWidth;
self.ctx.stroke();
self.ctx.restore();
}
};
self.createFireworks = function(
startX, startY, targetX, targetY
) {
self.fireworks.push(new Firework(
startX, startY, targetX,
targetY));
};
self.updateFireworks = function() {
let i = self.fireworks.length;
while (i--) {
let f = self.fireworks[i];
f.update(i);
}
};
self.drawFireworks = function() {
let i = self.fireworks.length;
while (i--) {
let f = self.fireworks[i];
f.draw();
}
};
self.bindEvents = function() {
$(window).on('resize',
function() {
clearTimeout(self.timeout);
self.timeout = setTimeout(
function() {
self.ctx.lineCap = 'round';
self.ctx.lineJoin =
'round';
}, 100);
});
$(self.canvas).on('mousedown',
function(e) {
let randLaunch = rand(0, 5);
self.mx = e.pageX - self
.canvasContainer.offset()
.left;
self.my = e.pageY - self
.canvasContainer.offset()
.top;
self.currentHue = rand(self
.hueMin, self.hueMax);
self.createFireworks(self.cw /
2, self.ch, self.mx, self.my
);
$(self.canvas).on(
'mousemove.fireworks',
function(e) {
let randLaunch = rand(0,
5);
self.mx = e.pageX - self
.canvasContainer.offset()
.left;
self.my = e.pageY - self
.canvasContainer.offset()
.top;
self.currentHue = rand(self
.hueMin, self.hueMax);
self.createFireworks(self
.cw / 2, self.ch, self
.mx, self.my);
});
});
$(self.canvas).on('mouseup',
function(e) {
$(self.canvas).off(
'mousemove.fireworks');
});
};
self.clear = function() {
self.particles = [];
self.fireworks = [];
self.ctx.clearRect(0, 0, self.cw,
self.ch);
};
self.updateDelta = function() {
let newTime = Date.now();
self.dt = (newTime - self
.oldTime) / 16;
self.dt = (self.dt > 5) ? 5 :
self.dt;
self.oldTime = newTime;
};
self.canvasLoop = function() {
requestAnimFrame(self.canvasLoop,
self.canvas);
self.updateDelta();
self.ctx
.globalCompositeOperation =
'destination-out';
self.ctx.fillStyle =
'rgba(0,0,0,' + self
.clearAlpha / 100 + ')';
self.ctx.fillRect(0, 0, self.cw,
self.ch);
self.ctx
.globalCompositeOperation =
'lighter';
self.updateFireworks();
self.updateParticles();
self.drawFireworks();
self.drawParticles();
};
self.init();
let initialLaunchCount = 120;
while (initialLaunchCount--) {
setTimeout(function() {
self.fireworks.push(
new Firework(self.cw / 2,
self.ch, rand(80, self.cw -
50), rand(50, self.ch /
2) - 50));
}, initialLaunchCount * 200);
}
};
let guiPresets = {
"preset": "Default",
"remembered": {
"Default": {
"0": {
"fworkSpeed": 2,
"fworkAccel": 3,
"showShockwave": true,
"showTarget": false,
"partCount": 30,
"partSpeed": 5,
"partSpeedVariance": 10,
"partWind": 50,
"partFriction": 5,
"partGravity": 1,
"flickerDensity": 20,
"hueMin": 150,
"hueMax": 200,
"hueVariance": 30,
"lineWidth": 1,
"clearAlpha": 25
}
}
},
"closed": true,
"folders": {
"Fireworks": {
"preset": "Default",
"closed": false,
"folders": {}
},
"Particles": {
"preset": "Default",
"closed": true,
"folders": {}
},
"Color": {
"preset": "Default",
"closed": true,
"folders": {}
},
"Other": {
"preset": "Default",
"closed": true,
"folders": {}
}
}
};
let fworks = new Fireworks();
let gui = new dat.GUI({
autoPlace: false,
load: guiPresets,
preset: 'Default'
});
let customContainer = document
.getElementById('gui');
customContainer.appendChild(gui
.domElement);
let guiFireworks = gui.addFolder(
'Fireworks');
guiFireworks.add(fworks,
'fworkSpeed').min(1).max(10).step(
1);
guiFireworks.add(fworks,
'fworkAccel').min(0).max(50).step(
1);
guiFireworks.add(fworks,
'showShockwave');
guiFireworks.add(fworks,
'showTarget');
let guiParticles = gui.addFolder(
'Particles');
guiParticles.add(fworks, 'partCount')
.min(0).max(500).step(1);
guiParticles.add(fworks, 'partSpeed')
.min(1).max(100).step(1);
guiParticles.add(fworks,
'partSpeedVariance').min(0).max(50)
.step(1);
guiParticles.add(fworks, 'partWind')
.min(0).max(100).step(1);
guiParticles.add(fworks,
'partFriction').min(0).max(50)
.step(1);
guiParticles.add(fworks,
'partGravity').min(-20).max(20)
.step(1);
guiParticles.add(fworks,
'flickerDensity').min(0).max(50)
.step(1);
let guiColor = gui.addFolder(
'Color');
guiColor.add(fworks, 'hueMin').min(0)
.max(360).step(1);
guiColor.add(fworks, 'hueMax').min(0)
.max(360).step(1);
guiColor.add(fworks, 'hueVariance')
.min(0).max(180).step(1);
let guiOther = gui.addFolder(
'Other');
guiOther.add(fworks, 'lineWidth')
.min(1).max(20).step(1);
guiOther.add(fworks, 'clearAlpha')
.min(0).max(100).step(1);
guiOther.add(fworks, 'clear').name(
'Clear');
gui.remember(fworks);
}(jQuery));
Explicação do código JavaScript:
- Canvas API: Criamos partículas que representam os fogos de artifício, com posições e velocidades aleatórias. Elas se movem para fora e diminuem a opacidade ao longo do tempo.
- Anime.js: Usamos Anime.js para animar a mensagem "Feliz Ano Novo 2025!" com um efeito de movimento para cima e transição suave de opacidade.
Passo 4: Visualizando o Resultado
Após seguir todos os passos, abra o arquivo index.html
em seu navegador. Você verá a animação de fogos de artifício junto com a mensagem "Feliz Ano Novo 2025!" que aparece com uma animação suave.
Melhorias Possíveis
- Adicionar sons: Você pode adicionar efeitos sonoros para simular fogos de artifício reais.
- Interatividade: Torne a animação interativa, permitindo que os usuários cliquem para iniciar os fogos de artifício.
- Mais efeitos: Experimente adicionar mais partículas ou variações nas cores e tamanhos dos fogos de artifício.
Tecnologias Utilizadas
Conclusão
Neste tutorial, aprendemos como criar uma animação de fogos de artifício para celebrar a chegada do Ano Novo, utilizando HTML5, CSS3, JavaScript, Anime.js e a Canvas API. Agora você pode personalizar e expandir este projeto para criar suas próprias celebrações de Ano Novo digitais!
Se preferir, os códigos estão no meu Repositório no GitHub.
Esse tutorial pode ser facilmente adaptado para outras ocasiões, como aniversários, festas ou eventos especiais.
Comentários
Postar um comentário
Obrigado pelo seu feedback!