<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>html5 canvas流星陨石掉落地面爆炸动画特效</title>
<style>
*{margin:0;padding:0;list-style-type:none;}
.page-meteor-canvas {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
background: -webkit-gradient(linear, left top, left bottom, from(#000000), to(#1e004e));
background: linear-gradient(to bottom, #000000, #1e004e);
}
.page-meteor-canvas:before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0.1;
background: url("img/night-sky.jpg") center bottom;
background-size: cover;
}
.page-meteor-canvas canvas {
position: absolute;
display: block;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.page-meteor-canvas #canvas {
mix-blend-mode: multiply;
}
.page-meteor-canvas #buffer {
-webkit-filter: blur(6px) brightness(10) contrast(1.25);
filter: blur(6px) brightness(10) contrast(1.25);
mix-blend-mode: screen;
}
</style>
</head>
<body>
<div class="page page-meteor-canvas">
<canvas id="buffer"></canvas>
<canvas id="canvas"></canvas>
</div>
<script>
"use strict";
function Meteor(options) {
options = options || {};
this.main = options.main || {};
this.ctx = options.ctx;
this.vfx = options.vfx;
this.position = options.position || {
x: Math.random() * this.main.width + this.main.width * 0.25,
y: Math.random() * -this.main.height * 0.25
};
this.rotation = options.rotation || Math.random() * 2 * Math.PI;
this.velocity = options.velocity || {
phi: Math.random() * 0.4 - 0.2 + Math.PI * 0.75,
length: Math.random() * 5 + 1,
rotate: Math.random() * 0.1 - 0.05
};
this.gravity = options.gravity || { phi: Math.PI * 0.5, length: 0.98 };
this.edge =
options.edge ||
~~(Math.random() * this.velocity.length + this.velocity.length * 2 + 2);
this.color = options.color || ["#300", "#610", "#fd2", "#f62"];
this.timescale = options.timescale || 0.5;
this.ground = options.ground || this.main.height;
this.accelerate = options.accelerate || {
phi: {
change: Math.random() * 0.015 - 0.0075,
min: Math.PI * 0.6,
max: Math.PI * 0.9
},
friction: 1.005
};
this.points = [...new Array(this.edge)].map((e, i) => {
var phi = i / this.edge * 2 * Math.PI;
return {
phi: phi + Math.random() * 0.4 - 0.2,
length: Math.random() * this.velocity.length * 2 + this.velocity.length * 4
};
});
this.update = function(i) {
this.rotation += this.velocity.rotate * this.timescale;
this.position.x +=
Math.cos(this.velocity.phi) * this.velocity.length * this.timescale;
this.position.y +=
Math.sin(this.velocity.phi) * this.velocity.length * this.timescale;
this.position.x +=
Math.cos(this.gravity.phi) * this.gravity.length * this.timescale;
this.position.y +=
Math.sin(this.gravity.phi) * this.gravity.length * this.timescale;
this.velocity.phi += this.accelerate.phi.change * this.timescale;
(this.velocity.phi > this.accelerate.phi.max ||
this.velocity.phi < this.accelerate.phi.min) &&
(this.accelerate.phi.change = -this.accelerate.phi.change);
this.velocity.length *= this.accelerate.friction;
if (this.position.y > this.ground) {
var ctx = this.vfx;
var position = this.position;
var range = this.edge;
var main = this.main;
[...new Array(this.edge)].map(
() =>
main.makeRock({
position: { x: position.x, y: position.y },
base: range
}) ||
LightFlare(
ctx,
position.x + Math.random() * 10 * range - 5 * range,
position.y + Math.random() * 6 * range - 3 * range,
range * Math.random() * 30 + 30
)
);
this.main.meteors.splice(i, 1);
this.main.makeMeteor();
}
};
this.render = function() {
var ctx = this.ctx;
ctx.strokeStyle = this.color[0];
ctx.fillStyle = this.color[1];
ctx.moveTo(this.position.x, this.position.y);
ctx.beginPath();
this.points.forEach(p => {
var x = Math.cos(p.phi + this.rotation) * p.length + this.position.x;
var y = Math.sin(p.phi + this.rotation) * p.length + this.position.y;
ctx.lineTo(x, y);
});
ctx.closePath();
ctx.stroke();
ctx.fill();
//
var vfx = this.vfx;
vfx.globalAlpha = Math.random();
vfx.fillStyle = this.color[2 + ~~(Math.random() + 0.6)];
vfx.moveTo(this.position.x, this.position.y);
vfx.beginPath();
this.points.forEach(p => {
var x =
Math.cos(p.phi + this.rotation * Math.random() * 0.2 - 0.1) * p.length +
Math.random() +
this.position.x;
var y =
Math.sin(p.phi + this.rotation * Math.random() * 0.2 - 0.1) * p.length +
Math.random() +
this.position.y;
vfx.lineTo(x, y);
});
vfx.closePath();
vfx.fill();
};
}
function Rock(options) {
options = options || {};
this.main = options.main || {};
this.base = options.base || 2;
this.ctx = options.ctx;
this.vfx = options.vfx;
this.position = options.position || {
x: Math.random() * this.main.width,
y: Math.random() * this.main.height
};
this.rotation = options.rotation || Math.random() * 2 * Math.PI;
this.velocity = options.velocity || {
phi: Math.random() * Math.PI * 0.5 + Math.PI * 1.25,
length: Math.random() * 1.5 * this.base + 15,
rotate: Math.random() * 0.2 - 0.1
};
this.gravity = options.gravity || { phi: Math.PI * 0.5, length: 5 };
this.edge =
options.edge ||
~~(Math.random() * this.velocity.length + this.velocity.length * 2 + 5);
this.color = options.color || ["#300", "#510", "#fd2", "#f62"];
this.timescale = options.timescale || 1.5;
this.lifespan = options.lifespan || ~~(Math.random() * 30 + 10);
this.friction = 0.94;
this.points = [...new Array(this.edge)].map((e, i) => {
var phi = i / this.edge * 2 * Math.PI;
return {
phi: phi + Math.random() * 0.4 - 0.2,
length:
Math.random() * this.velocity.length * 0.1 + this.velocity.length * 0.2
};
});
this.update = function(i) {
if (this.lifespan-- <= 0) {
this.main.meteors.splice(i, 1);
return;
}
this.rotation += this.velocity.rotate * this.timescale;
this.position.x +=
Math.cos(this.velocity.phi) * this.velocity.length * this.timescale;
this.position.y +=
Math.sin(this.velocity.phi) * this.velocity.length * this.timescale;
this.position.x +=
Math.cos(this.gravity.phi) * this.gravity.length * this.timescale;
this.position.y +=
Math.sin(this.gravity.phi) * this.gravity.length * this.timescale;
this.velocity.length *= this.friction;
};
this.render = function() {
var ctx = this.ctx;
ctx.globalAlpha = Math.random();
ctx.strokeStyle = this.color[0];
ctx.fillStyle = this.color[1];
ctx.moveTo(this.position.x, this.position.y);
ctx.beginPath();
this.points.forEach(p => {
var x = Math.cos(p.phi + this.rotation) * p.length + this.position.x;
var y = Math.sin(p.phi + this.rotation) * p.length + this.position.y;
ctx.lineTo(x, y);
});
ctx.closePath();
ctx.stroke();
ctx.fill();
ctx.globalAlpha = 1;
//
var vfx = this.vfx;
vfx.globalAlpha = Math.random();
vfx.fillStyle = this.color[2 + ~~(Math.random() + 0.6)];
vfx.moveTo(this.position.x, this.position.y);
vfx.beginPath();
this.points.forEach(p => {
var x =
Math.cos(p.phi + this.rotation * Math.random() * 0.2 - 0.1) * p.length +
Math.random() +
this.position.x;
var y =
Math.sin(p.phi + this.rotation * Math.random() * 0.2 - 0.1) * p.length +
Math.random() +
this.position.y;
vfx.lineTo(x, y);
});
vfx.closePath();
vfx.fill();
};
}
function LightFlare(ctx, x, y, range) {
range = range || 100;
const strength = Math.random() * range + range;
const light = ctx.createRadialGradient(x, y, 0, x, y, strength);
light.addColorStop(0, "rgba(250, 200, 50, 0.4)");
light.addColorStop(0.1, "rgba(250, 200