<!DOCTYPE html>
<html class=
" -webkit- js flexbox canvas canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio localstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths"
>
<head>
<meta charset=
"UTF-8"
>
<meta name=
"viewport"
content=
" initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
>
<meta name=
"format-detection"
content=
"telephone=no"
>
<title>HTML5/WebGL高性能烟花绽放动画</title>
<meta name=
"description"
content=
"HTML5/WebGL高性能烟花绽放动画"
>
<meta name=
"keywords"
content=
"HTML5/WebGL高性能烟花绽放动画"
>
<style>
canvas {
position: absolute;
top: 0;
left: 0;
background-color:
#111;
}
</style>
</head>
<body>
<script src=
"jquery-1.11.1.min.js"
></script>
<canvas id=
"c"
width=
"1440"
height=
"304"
></canvas><script src=
"prefixfree.min.js"
></script><script src=
"modernizr.js"
></script><script>
var
gl = c.getContext(&
#39;webgl', {
preserveDrawingBuffer:
true
}),
w = c.width = window.innerWidth,
h = c.height = window.innerHeight
, webgl = {}, opts = {
projectileAlpha: .8,
projectileLineWidth: 1.3,
fireworkAngleSpan: .5,
baseFireworkVel: 3,
addedFireworkVel: 3,
gravity: .03,
lowVelBoundary: -.2,
xFriction: .995,
baseShardVel: 1,
addedShardVel: .2,
fireworks: 1000,
baseShardsParFirework: 10,
addedShardsParFirework: 10,
shardFireworkVelMultiplier: .3,
initHueMultiplier: 1 / 360,
runHueAdder: .1 / 360
}
webgl.vertexShaderSource = `
uniform int u_mode;
uniform vec2 u_res;
attribute vec4 a_data;
varying vec4 v_color;
vec3 h2rgb( float h ){
return
clamp( abs( mod( h * 6. + vec3( 0, 4, 2 ), 6. ) - 3. ) -1., 0., 1. );
}
void clear(){
gl_Position = vec4( a_data.xy, 0, 1 );
v_color = vec4( 0, 0, 0, a_data.w );
}
void draw(){
gl_Position = vec4( vec2( 1, -1 ) * ( ( a_data.xy / u_res ) * 2. - 1. ), 0, 1 );
v_color = vec4( h2rgb( a_data.z ), a_data.w );
}
void main(){
if
( u_mode == 0 )
draw();
else
clear();
}
`;
webgl.fragmentShaderSource = `
precision mediump float;
varying vec4 v_color;
void main(){
gl_FragColor = v_color;
}
`;
webgl.vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(webgl.vertexShader, webgl.vertexShaderSource);
gl.compileShader(webgl.vertexShader);
webgl.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(webgl.fragmentShader, webgl.fragmentShaderSource);
gl.compileShader(webgl.fragmentShader);
webgl.shaderProgram = gl.createProgram();
gl.attachShader(webgl.shaderProgram, webgl.vertexShader);
gl.attachShader(webgl.shaderProgram, webgl.fragmentShader);
gl.linkProgram(webgl.shaderProgram);
gl.useProgram(webgl.shaderProgram);
webgl.dataAttribLoc = gl.getAttribLocation(webgl.shaderProgram, &
#39;a_data');
webgl.dataBuffer = gl.createBuffer();
gl.enableVertexAttribArray(webgl.dataAttribLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, webgl.dataBuffer);
gl.vertexAttribPointer(webgl.dataAttribLoc, 4, gl.FLOAT,
false
, 0, 0);
webgl.resUniformLoc = gl.getUniformLocation(webgl.shaderProgram, &
#39;u_res');
webgl.modeUniformLoc = gl.getUniformLocation(webgl.shaderProgram, &
#39;u_mode');
gl.viewport(0, 0, w, h);
gl.uniform2f(webgl.resUniformLoc, w, h);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
gl.lineWidth(opts.projectileLineWidth);
webgl.data = [];
webgl.clear =
function
() {
gl.uniform1i(webgl.modeUniformLoc, 1);
var
a = .1;
webgl.data = [-1, -1, 0, a,
1, -1, 0, a, -1, 1, 0, a, -1, 1, 0, a,
1, -1, 0, a,
1, 1, 0, a
];
webgl.draw(gl.TRIANGLES);
gl.uniform1i(webgl.modeUniformLoc, 0);
webgl.data.length = 0;
}
webgl.draw =
function
(glType) {
gl.bufferData(gl.ARRAY_BUFFER,
new
Float32Array(webgl.data), gl.STATIC_DRAW);
gl.drawArrays(glType, 0, webgl.data.length / 4);
}
var
fireworks = [],
tick = 0,
sins = [],
coss = [],
maxShardsParFirework = opts.baseShardsParFirework + opts.addedShardsParFirework,
tau = 6.283185307179586476925286766559;
for
(
var
i = 0; i < maxShardsParFirework; ++i) {
sins[i] = Math.sin(tau * i / maxShardsParFirework);
coss[i] = Math.cos(tau * i / maxShardsParFirework);
}
function
Firework() {
this
.reset();
this
.shards = [];
for
(
var
i = 0; i < maxShardsParFirework; ++i)
this
.shards.push(
new
Shard(
this
));
}
Firework.prototype.reset =
function
() {
var
angle = -Math.PI / 2 + (Math.random() - .5) * opts.fireworkAngleSpan,
vel = opts.baseFireworkVel + opts.addedFireworkVel * Math.random();
this
.mode = 0;
this
.vx = vel * Math.cos(angle);
this
.vy = vel * Math.sin(angle);
this
.x = Math.random() * w;
this
.y = h;
this
.hue = tick * opts.initHueMultiplier;
}
Firework.prototype.step =
function
() {
if
(
this
.mode === 0) {
var
ph =
this
.hue,
px =
this
.x,
py =
this
.y;
this
.hue += opts.runHueAdder;
this
.x +=
this
.vx *= opts.xFriction;
this
.y +=
this
.vy += opts.gravity;
webgl.data.push(
px, py, ph, opts.projectileAlpha * .2,
this
.x,
this
.y,
this
.hue, opts.projectileAlpha * .2);
if
(
this
.vy >= opts.lowVelBoundary) {
this
.mode = 1;
this
.shardAmount = opts.baseShardsParFirework + opts.addedShardsParFirework * Math.random() | 0;
var
baseAngle = Math.random() * tau,
x = Math.cos(baseAngle),
y = Math.sin(baseAngle),
sin = sins[
this
.shardAmount],
cos = coss[
this
.shardAmount];
for
(
var
i = 0; i <
this
.shardAmount; ++i) {
var
vel = opts.baseShardVel + opts.addedShardVel * Math.random();
this
.shards[i].reset(x * vel, y * vel)
var
X = x;
x = x * cos - y * sin;
y = y * cos + X * sin;
}
}
}
else
if
(
this
.mode === 1) {
this
.ph =
this
.hue
this
.hue += opts.runHueAdder;
var
allDead =
true
;
for
(
var
i = 0; i <
this
.shardAmount; ++i) {
var
shard =
this
.shards[i];
if
(!shard.dead) {
shard.step();
allDead =
false
;
}
}
if
(allDead)
this
.reset();
}
}
function
Shard(parent) {
this
.parent = parent;
}
Shard.prototype.reset =
function
(vx, vy) {
this
.x =
this
.parent.x;
this
.y =
this
.parent.y;
this
.vx =
this
.parent.vx * opts.shardFireworkVelMultiplier + vx;
this
.vy =
this
.parent.vy * opts.shardFireworkVelMultiplier + vy;
this
.starty =
this
.y;
this
.dead =
false
;
this
.tick = 1;
}
Shard.prototype.step =
function
() {
this
.tick += .05;
var
px =
this
.x,
py =
this
.y;
this
.x +=
this
.vx *= opts.xFriction;
this
.y +=
this
.vy += opts.gravity;
var
proportion = 1 - (
this
.y -
this
.starty) / (h -
this
.starty);
webgl.data.push(
px, py,
this
.parent.ph, opts.projectileAlpha /
this
.tick,
this
.x,
this
.y,
this
.parent.hue, opts.projectileAlpha /
this
.tick);
if
(
this
.y > h)
this
.dead =
true
;
}
function
anim() {
window.requestAnimationFrame(anim)
webgl.clear();
++tick;
if
(fireworks.length < opts.fireworks)
fireworks.push(
new
Firework);
fireworks.map(
function
(firework) {
firework.step();
});
webgl.draw(gl.LINES);
}
anim();
window.addEventListener(&
#39;resize', function() {
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
gl.viewport(0, 0, w, h);
gl.uniform2f(webgl.resUniformLoc, w, h);
})
window.addEventListener(&
#39;click', function(e) {
var
firework =
new
Firework();
firework.x = e.clientX;
firework.y = e.clientY;
firework.vx = 0;
firework.vy = 0;
fireworks.push(firework);
});
</script>
</body>
</html>