canvas可以实现不同动画效果,本文主要记录几种不同节日烟花效果实现。
实现一
html
<canvas id="canvas"></canvas>
css
body {
background: #000;
margin: 0;
}
canvas {
cursor: crosshair;
display: block;
}
js
// when animating on canvas, it is best to use requestAnimationFrame instead of setTimeout or setInterval
// not supported in all browsers though and sometimes needs a prefix, so we need a shim
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
// now we will setup our basic variables for the demo
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
// full screen dimensions
cw = window.innerWidth,
ch = window.innerHeight,
// firework collection
fireworks = [],
// particle collection
particles = [],
// starting hue
hue = 120,
// when launching fireworks with a click, too many get launched at once without a limiter, one launch per 5 loop ticks
limiterTotal = 5,
limiterTick = 0,
// this will time the auto launches of fireworks, one launch per 80 loop ticks
timerTotal = 80,
timerTick = 0,
mousedown = false,
// mouse x coordinate,
mx,
// mouse y coordinate
my;
// set canvas dimensions
canvas.width = cw;
canvas.height = ch;
// now we are going to setup our function placeholders for the entire demo
// get a random number within a range
function random(min, max) {
return Math.random() * (max - min) + min;
}
// calculate the distance between two points
function calculateDistance(p1x, p1y, p2x, p2y) {
var xDistance = p1x - p2x,
yDistance = p1y - p2y;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
// create firework
function Firework(sx, sy, tx, ty) {
// actual coordinates
this.x = sx;
this.y = sy;
// starting coordinates
this.sx = sx;
this.sy = sy;
// target coordinates
this.tx = tx;
this.ty = ty;
// distance from starting point to target
this.distanceToTarget = calculateDistance(sx, sy, tx, ty);
this.distanceTraveled = 0;
// track the past coordinates of each firework to create a trail effect, increase the coordinate count to create more prominent trails
this.coordinates = [];
this.coordinateCount = 3;
// populate initial coordinate collection with the current coordinates
while (this.coordinateCount--) {
this.coordinates.push([this.x, this.y]);
}
this.angle = Math.atan2(ty - sy, tx - sx);
this.speed = 2;
this.acceleration = 1.05;
this.brightness = random(50, 70);
// circle target indicator radius
this.targetRadius = 1;
}
// update firework
Firework.prototype.update = function (index) {
// remove last item in coordinates array
this.coordinates.pop();
// add current coordinates to the start of the array
this.coordinates.unshift([this.x, this.y]);
// cycle the circle target indicator radius
if (this.targetRadius < 8) {
this.targetRadius += 0.3;
} else {
this.targetRadius = 1;
}
// speed up the firework
this.speed *= this.acceleration;
// get the current velocities based on angle and speed
var vx = Math.cos(this.angle) * this.speed,
vy = Math.sin(this.angle) * this.speed;
// how far will the firework have traveled with velocities applied?
this.distanceTraveled = calculateDistance(this.sx, this.sy, this.x + vx, this.y + vy);
// if the distance traveled, including velocities, is greater than the initial distance to the target, then the target has been reached
if (this.distanceTraveled >= this.distanceToTarget) {
createParticles(this.tx, this.ty);
// remove the firework, use the index passed into the update function to determine which to remove
fireworks.splice(index, 1);
} else {
// target not reached, keep traveling
this.x += vx;
this.y += vy;
}
}
// draw firework
Firework.prototype.draw = function () {
ctx.beginPath();
// move to the last tracked coordinate in the set, then draw a line to the current x and y
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0], this.coordinates[this.coordinates.length - 1][
1
]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = 'hsl(' + hue + ', 100%, ' + this.brightness + '%)';
ctx.stroke();
ctx.beginPath();
// draw the target for this firework with a pulsing circle
ctx.arc(this.tx, this.ty, this.targetRadius, 0, Math.PI * 2);
ctx.stroke();
}
// create particle
function Particle(x, y) {
this.x = x;
this.y = y;
// track the past coordinates of each particle to create a trail effect, increase the coordinate count to create more prominent trails
this.coordinates = [];
this.coordinateCount = 5;
while (this.coordinateCount--) {
this.coordinates.push([this.x, this.y]);
}
// set a random angle in all possible directions, in radians
this.angle = random(0, Math.PI * 2);
this.speed = random(1, 10);
// friction will slow the particle down
this.friction = 0.95;
// gravity will be applied and pull the particle down
this.gravity = 1;
// set the hue to a random number +-20 of the overall hue variable
this.hue = random(hue - 20, hue + 20);
this.brightness = random(50, 80);
this.alpha = 1;
// set how fast the particle fades out
this.decay = random(0.015, 0.03);
}
// update particle
Particle.prototype.update = function (index) {
// remove last item in coordinates array
this.coordinates.pop();
// add current coordinates to the start of the array
this.coordinates.unshift([this.x, this.y]);
// slow down the particle
this.speed *= this.friction;
// apply velocity
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed + this.gravity;
// fade out the particle
this.alpha -= this.decay;
// remove the particle once the alpha is low enough, based on the passed in index
if (this.alpha <= this.decay) {
particles.splice(index, 1);
}
}
// draw particle
Particle.prototype.draw = function () {
ctx.beginPath();
// move to the last tracked coordinates in the set, then draw a line to the current x and y
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0], this.coordinates[this.coordinates.length - 1][
1
]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = 'hsla(' + this.hue + ', 100%, ' + this.brightness + '%, ' + this.alpha + ')';
ctx.stroke();
}
// create particle group/explosion
function createParticles(x, y) {
// increase the particle count for a bigger explosion, beware of the canvas performance hit with the increased particles though
var particleCount = 30;
while (particleCount--) {
particles.push(new Particle(x, y));
}
}
// main demo loop
function loop() {
// this function will run endlessly with requestAnimationFrame
requestAnimFrame(loop);
// increase the hue to get different colored fireworks over time
hue += 0.5;
// normally, clearRect() would be used to clear the canvas
// we want to create a trailing effect though
// setting the composite operation to destination-out will allow us to clear the canvas at a specific opacity, rather than wiping it entirely
ctx.globalCompositeOperation = 'destination-out';
// decrease the alpha property to create more prominent trails
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, cw, ch);
// change the composite operation back to our main mode
// lighter creates bright highlight points as the fireworks and particles overlap each other
ctx.globalCompositeOperation = 'lighter';
var text = "HAPPY NEW YEAR !";
ctx.font = "50px sans-serif";
var textData = ctx.measureText(text);
ctx.fillStyle = "rgba(" + parseInt(random(0, 255)) + "," + parseInt(random(0, 255)) + "," + parseInt(random(0,
255)) + ",0.3)";
ctx.fillText(text, cw / 2 - textData.width / 2, ch / 2);
// loop over each firework, draw it, update it
var i = fireworks.length;
while (i--) {
fireworks[i].draw();
fireworks[i].update(i);
}
// loop over each particle, draw it, update it
var i = particles.length;
while (i--) {
particles[i].draw();
particles[i].update(i);
}
// launch fireworks automatically to random coordinates, when the mouse isn't down
if (timerTick >= timerTotal) {
if (!mousedown) {
// start the firework at the bottom middle of the screen, then set the random target coordinates, the random y coordinates will be set within the range of the top half of the screen
for (var h = 0; h < 50; h++) {
fireworks.push(new Firework(cw / 2, ch / 2, random(0, cw), random(0, ch)));
}
timerTick = 0;
}
} else {
timerTick++;
}
// limit the rate at which fireworks get launched when mouse is down
if (limiterTick >= limiterTotal) {
if (mousedown) {
// start the firework at the bottom middle of the screen, then set the current mouse coordinates as the target
fireworks.push(new Firework(cw / 2, ch / 2, mx, my));
limiterTick = 0;
}
} else {
limiterTick++;
}
}
// mouse event bindings
// update the mouse coordinates on mousemove
canvas.addEventListener('mousemove', function (e) {
mx = e.pageX - canvas.offsetLeft;
my = e.pageY - canvas.offsetTop;
});
// toggle mousedown state and prevent canvas from being selected
canvas.addEventListener('mousedown', function (e) {
e.preventDefault();
mousedown = true;
});
canvas.addEventListener('mouseup', function (e) {
e.preventDefault();
mousedown = false;
});
// once the window loads, we are ready for some fireworks!
window.onload = loop;
实现二
效果地址
html
<canvas></canvas>
<h1>201<span>8</span></h1>
css
html,
body {
padding: 0px;
margin: 0px;
background: #222;
font-family: 'Karla', sans-serif;
color: #FFF;
height: 100%;
overflow: hidden;
}
h1 {
z-index: 1000;
position: fixed;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-100%);
font-size: 58px;
overflow: hidden;
}
span {
position: relative;
display: inline-block;
animation: drop 0.75s ease 0s;
}
canvas {
width: 100%;
height: 100%;
}
@keyframes drop {
0% {
transform: translateY(-100px);
opacity: 0;
}
90% {
opacity: 1;
transform: translateY(10px);
}
100% {
transform: translateY(0px);
}
}
js
var ctx = document.querySelector('canvas').getContext('2d')
ctx.canvas.width = window.innerWidth
ctx.canvas.height = window.innerHeight
var sparks = []
var fireworks = []
var i = 20;
while (i--) {
fireworks.push(
new Firework(Math.random() * window.innerWidth, window.innerHeight * Math.random())
)
}
render()
function render() {
setTimeout(render, 1000 / 60)
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
for (var firework of fireworks) {
if (firework.dead) continue
firework.move()
firework.draw()
}
for (var spark of sparks) {
if (spark.dead) continue
spark.move()
spark.draw()
}
if (Math.random() < 0.05) {
fireworks.push(new Firework())
}
}
function Spark(x, y, color) {
this.x = x
this.y = y
this.dir = Math.random() * (Math.PI * 2)
this.dead = false
this.color = color
this.speed = Math.random() * 3 + 3;
this.walker = new Walker({
radius: 20,
speed: 0.25
})
this.gravity = 0.25
this.dur = this.speed / 0.1
this.move = function () {
this.dur--
if (this.dur < 0) this.dead = true
if (this.speed < 0) return
if (this.speed > 0) this.speed -= 0.1
var walk = this.walker.step()
this.x += Math.cos(this.dir + walk) * this.speed
this.y += Math.sin(this.dir + walk) * this.speed
this.y += this.gravity
this.gravity += 0.05
}
this.draw = function () {
drawCircle(this.x, this.y, 3, this.color)
}
}
function Firework(x, y) {
this.xmove = new Walker({
radius: 10,
speed: 0.5
})
this.x = x || Math.random() * ctx.canvas.width
this.y = y || ctx.canvas.height
this.height = Math.random() * ctx.canvas.height / 2
this.dead = false
this.color = randomColor()
this.move = function () {
this.x += this.xmove.step()
if (this.y > this.height) this.y -= 1;
else this.burst()
}
this.draw = function () {
drawCircle(this.x, this.y, 1, this.color)
}
this.burst = function () {
this.dead = true
var i = 100;
while (i--) sparks.push(new Spark(this.x, this.y, this.color))
}
}
function drawCircle(x, y, radius, color) {
color = color || '#FFF'
ctx.fillStyle = color
ctx.fillRect(x - radius / 2, y - radius / 2, radius, radius)
}
function randomColor() {
return ['#6ae5ab', '#88e3b2', '#36b89b', '#7bd7ec', '#66cbe1'][Math.floor(Math.random() * 5)];
}
function Walker(options) {
this.step = function () {
this.direction = Math.sign(this.target) * this.speed
this.value += this.direction
this.target ?
this.target -= this.direction :
(this.value) ?
(this.wander) ?
this.target = this.newTarget() :
this.target = -this.value :
this.target = this.newTarget()
return this.direction
}
this.newTarget = function () {
return Math.round(Math.random() * (this.radius * 2) - this.radius)
}
this.start = 0
this.value = 0
this.radius = options.radius
this.target = this.newTarget()
this.direction = Math.sign(this.target)
this.wander = options.wander
this.speed = options.speed || 1
}
实现三
效果地址
html
<canvas id="cas" style="background-color:rgba(0,5,24,1)" width="1235" height="680">浏览器不支持canvas</canvas>
<div class="city">
<img src="city.png" alt="">
</div>
<img src="moon.png" alt="" id="moon" style="visibility: hidden;">
<div style="display:none">
<div class="shape">新年快乐</div>
<div class="shape">阖家欢乐</div>
<div class="shape">万事如意</div>
<div class="shape">心想事成</div>
</div>
css
body {
margin: 0;
padding: 0;
overflow: hidden;
}
.city {
width: 100%;
position: fixed;
bottom: 0px;
z-index: 100;
}
.city img {
width: 100%;
}
js
var canvas = document.getElementById("cas");
var ocas = document.createElement("canvas");
var octx = ocas.getContext("2d");
var ctx = canvas.getContext("2d");
ocas.width = canvas.width = window.innerWidth;
ocas.height = canvas.height = window.innerHeight;
var bigbooms = [];
window.onload = function () {
initAnimate()
}
function initAnimate() {
drawBg();
lastTime = new Date();
animate();
}
var lastTime;
function animate() {
ctx.save();
ctx.fillStyle = "rgba(0,5,24,0.1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
var newTime = new Date();
if (newTime - lastTime > 500 + (window.innerHeight - 767) / 2) {
var random = Math.random() * 100 > 2 ? true : false;
var x = getRandom(canvas.width / 5, canvas.width * 4 / 5);
var y = getRandom(50, 200);
if (random) {
var bigboom = new Boom(getRandom(canvas.width / 3, canvas.width * 2 / 3), 2, "#FFF", {
x: x,
y: y
});
bigbooms.push(bigboom)
} else {
var bigboom = new Boom(getRandom(canvas.width / 3, canvas.width * 2 / 3), 2, "#FFF", {
x: canvas.width / 2,
y: 200
}, document.querySelectorAll(".shape")[parseInt(getRandom(0, document.querySelectorAll(
".shape").length))]);
bigbooms.push(bigboom)
}
lastTime = newTime;
}
stars.foreach(function () {
this.paint();
})
drawMoon();
bigbooms.foreach(function (index) {
var that = this;
if (!this.dead) {
this._move();
this._drawLight();
} else {
this.booms.foreach(function (index) {
if (!this.dead) {
this.moveTo(index);
} else if (index === that.booms.length - 1) {
bigbooms[bigbooms.indexOf(that)] = null;
}
})
}
});
raf(animate);
}
function drawMoon() {
var moon = document.getElementById("moon");
var centerX = canvas.width - 200,
centerY = 100,
width = 80;
if (moon.complete) {
ctx.drawImage(moon, centerX, centerY, width, width)
} else {
moon.onload = function () {
ctx.drawImage(moon, centerX, centerY, width, width)
}
}
var index = 0;
for (var i = 0; i < 10; i++) {
ctx.save();
ctx.beginPath();
ctx.arc(centerX + width / 2, centerY + width / 2, width / 2 + index, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(240,219,120,0.005)";
index += 2;
ctx.fill();
ctx.restore();
}
}
Array.prototype.foreach = function (callback) {
for (var i = 0; i < this.length; i++) {
if (this[i] !== null) callback.apply(this[i], [i])
}
}
var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
canvas.onclick = function () {
var x = event.clientX;
var y = event.clientY;
var bigboom = new Boom(getRandom(canvas.width / 3, canvas.width * 2 / 3), 2, "#FFF", {
x: x,
y: y
});
bigbooms.push(bigboom)
}
var Boom = function (x, r, c, boomArea, shape) {
this.booms = [];
this.x = x;
this.y = (canvas.height + r);
this.r = r;
this.c = c;
this.shape = shape || false;
this.boomArea = boomArea;
this.theta = 0;
this.dead = false;
this.ba = parseInt(getRandom(80, 200));
}
Boom.prototype = {
_paint: function () {
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.c;
ctx.fill();
ctx.restore();
},
_move: function () {
var dx = this.boomArea.x - this.x,
dy = this.boomArea.y - this.y;
this.x = this.x + dx * 0.01;
this.y = this.y + dy * 0.01;
if (Math.abs(dx) <= this.ba && Math.abs(dy) <= this.ba) {
if (this.shape) {
this._shapBoom();
} else this._boom();
this.dead = true;
} else {
this._paint();
}
},
_drawLight: function () {
ctx.save();
ctx.fillStyle = "rgba(255,228,150,0.3)";
ctx.beginPath();
ctx.arc(this.x, this.y, this.r + 3 * Math.random() + 1, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
},
_boom: function () {
var fragNum = getRandom(30, 200);
var style = getRandom(0, 10) >= 5 ? 1 : 2;
var color;
if (style === 1) {
color = {
a: parseInt(getRandom(128, 255)),
b: parseInt(getRandom(128, 255)),
c: parseInt(getRandom(128, 255))
}
}
var fanwei = parseInt(getRandom(300, 400));
for (var i = 0; i < fragNum; i++) {
if (style === 2) {
color = {
a: parseInt(getRandom(128, 255)),
b: parseInt(getRandom(128, 255)),
c: parseInt(getRandom(128, 255))
}
}
var a = getRandom(-Math.PI, Math.PI);
var x = getRandom(0, fanwei) * Math.cos(a) + this.x;
var y = getRandom(0, fanwei) * Math.sin(a) + this.y;
var radius = getRandom(0, 2)
var frag = new Frag(this.x, this.y, radius, color, x, y);
this.booms.push(frag);
}
},
_shapBoom: function () {
var that = this;
putValue(ocas, octx, this.shape, 5, function (dots) {
var dx = canvas.width / 2 - that.x;
var dy = canvas.height / 2 - that.y;
for (var i = 0; i < dots.length; i++) {
color = {
a: dots[i].a,
b: dots[i].b,
c: dots[i].c
}
var x = dots[i].x;
var y = dots[i].y;
var radius = 1;
var frag = new Frag(that.x, that.y, radius, color, x - dx, y - dy);
that.booms.push(frag);
}
})
}
}
function putValue(canvas, context, ele, dr, callback) {
context.clearRect(0, 0, canvas.width, canvas.height);
var img = new Image();
if (ele.innerHTML.indexOf("img") >= 0) {
img.src = ele.getElementsByTagName("img")[0].src;
imgload(img, function () {
context.drawImage(img, canvas.width / 2 - img.width / 2, canvas.height / 2 - img.width / 2);
dots = getimgData(canvas, context, dr);
callback(dots);
})
} else {
var text = ele.innerHTML;
context.save();
var fontSize = 200;
context.font = fontSize + "px 宋体 bold";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "rgba(" + parseInt(getRandom(128, 255)) + "," + parseInt(getRandom(128, 255)) + "," +
parseInt(getRandom(128, 255)) + " , 1)";
context.fillText(text, canvas.width / 2, canvas.height / 2);
context.restore();
dots = getimgData(canvas, context, dr);
callback(dots);
}
}
function imgload(img, callback) {
if (img.complete) {
callback.call(img);
} else {
img.onload = function () {
callback.call(this);
}
}
}
function getimgData(canvas, context, dr) {
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
context.clearRect(0, 0, canvas.width, canvas.height);
var dots = [];
for (var x = 0; x < imgData.width; x += dr) {
for (var y = 0; y < imgData.height; y += dr) {
var i = (y * imgData.width + x) * 4;
if (imgData.data[i + 3] > 128) {
var dot = {
x: x,
y: y,
a: imgData.data[i],
b: imgData.data[i + 1],
c: imgData.data[i + 2]
};
dots.push(dot);
}
}
}
return dots;
}
function getRandom(a, b) {
return Math.random() * (b - a) + a;
}
var maxRadius = 1,
stars = [];
function drawBg() {
for (var i = 0; i < 100; i++) {
var r = Math.random() * maxRadius;
var x = Math.random() * canvas.width;
var y = Math.random() * 2 * canvas.height - canvas.height;
var star = new Star(x, y, r);
stars.push(star);
star.paint()
}
}
var Star = function (x, y, r) {
this.x = x;
this.y = y;
this.r = r;
}
Star.prototype = {
paint: function () {
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255,255,255," + this.r + ")";
ctx.fill();
ctx.restore();
}
}
var focallength = 250;
var Frag = function (centerX, centerY, radius, color, tx, ty) {
this.tx = tx;
this.ty = ty;
this.x = centerX;
this.y = centerY;
this.dead = false;
this.centerX = centerX;
this.centerY = centerY;
this.radius = radius;
this.color = color;
}
Frag.prototype = {
paint: function () {
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(" + this.color.a + "," + this.color.b + "," + this.color.c + ",1)";
ctx.fill()
ctx.restore();
},
moveTo: function (index) {
this.ty = this.ty + 0.3;
var dx = this.tx - this.x,
dy = this.ty - this.y;
this.x = Math.abs(dx) < 0.1 ? this.tx : (this.x + dx * 0.1);
this.y = Math.abs(dy) < 0.1 ? this.ty : (this.y + dy * 0.1);
if (dx === 0 && Math.abs(dy) <= 80) {
this.dead = true;
}
this.paint();
}
}
总结
几种不同canvas实现烟花动画,基本分为烟火类和碎屑类展现烟花的不同状态。
同时,祝大家新年快乐!