快过年了,用五种不同的 JS 特效带你看烟花
- 2022 年 1 月 14 日
本文字数:9584 字
阅读完需:约 31 分钟
今天给大家带来几个好看的基于 HTML+CSS+JS 的烟花特效
雪花 、 樱花 、 浪漫贺卡 以及 圣诞树 我前段时间刚做过,感兴趣的也可以看看我前几期的博客:
雪花:跟小海一起看下雪——用 HTML、CSS 和 JS 实现简单的下雪特效
圣诞树和贺卡:https://xie.infoq.cn/article/04369b6c40349fc1d025f9370
🎇 五种不同的 JS 烟花特效
🍕 一、简单大气的烟花
演示地址:http://haiyong.site/fireworks1
HTML 代码:
这里的 HTML 代码很简短
<div>
<canvas id="canvas"></canvas>
</div>
CSS 代码
css 也只有这两段内容
body{
background:black;
overflow:hidden;
margin:0;
}
canvas{
background:#000;
}
JS 代码
所有的源码都在这里了,复制粘贴即可
window.addEventListener("resize", resizeCanvas, false);
window.addEventListener("DOMContentLoaded", onLoad, false);
window.requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000/60);
};
var canvas, ctx, w, h, particles = [], probability = 0.04,
xPoint, yPoint;
function onLoad() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
resizeCanvas();
window.requestAnimationFrame(updateWorld);
}
function resizeCanvas() {
if (!!canvas) {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
}
}
function updateWorld() {
update();
paint();
window.requestAnimationFrame(updateWorld);
}
function update() {
if (particles.length < 500 && Math.random() < probability) {
createFirework();
}
var alive = [];
for (var i=0; i<particles.length; i++) {
if (particles[i].move()) {
alive.push(particles[i]);
}
}
particles = alive;
}
function paint() {
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = "rgba(0,0,0,0.2)";
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'lighter';
for (var i=0; i<particles.length; i++) {
particles[i].draw(ctx);
}
}
function createFirework() {
xPoint = Math.random()*(w-200)+100;
yPoint = Math.random()*(h-200)+100;
var nFire = Math.random()*50+100;
var c = "rgb("+(~~(Math.random()*200+55))+","
+(~~(Math.random()*200+55))+","+(~~(Math.random()*200+55))+")";
for (var i=0; i<nFire; i++) {
var particle = new Particle();
particle.color = c;
var vy = Math.sqrt(25-particle.vx*particle.vx);
if (Math.abs(particle.vy) > vy) {
particle.vy = particle.vy>0 ? vy: -vy;
}
particles.push(particle);
}
}
function Particle() {
this.w = this.h = Math.random()*4+1;
this.x = xPoint-this.w/2;
this.y = yPoint-this.h/2;
this.vx = (Math.random()-0.5)*10;
this.vy = (Math.random()-0.5)*10;
this.alpha = Math.random()*.5+.5;
this.color;
}
Particle.prototype = {
gravity: 0.05,
move: function () {
this.x += this.vx;
this.vy += this.gravity;
this.y += this.vy;
this.alpha -= 0.01;
if (this.x <= -this.w || this.x >= screen.width ||
this.y >= screen.height ||
this.alpha <= 0) {
return false;
}
return true;
},
draw: function (c) {
c.save();
c.beginPath();
c.translate(this.x+this.w/2, this.y+this.h/2);
c.arc(0, 0, this.w, 0, Math.PI*2);
c.fillStyle = this.color;
c.globalAlpha = this.alpha;
c.closePath();
c.fill();
c.restore();
}
}
🍜 二、在农村看到的烟花
演示地址:http://haiyong.site/fireworks2(需要使用电脑打开,没做响应式手机端打开一片黑,或者可以看看后面的烟花)
HTML 代码:
这里的 HTML 代码还是一样的简短
<div id="jsi-fireworks-container" class="container"></div>
CSS 代码
css 也只有这三段内容
html, body{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #101010;
}
.container{
position: absolute;
width: 500px;
height: 500px;
top: 50%;
left: 50%;
margin-top: -250px;
margin-left: -250px;
}
canvas{
position: absolute;
top: 0;
left: 0;
}
JS 代码
JS 代码比较长,我这里放了一部分,需要完整源码的可以关注公众号【海拥】回复【烟花】
var RENDERER = {
LEAF_INTERVAL_RANGE : {min : 100, max : 200},
FIREWORK_INTERVAL_RANGE : {min : 20, max : 200},
SKY_COLOR : 'hsla(210, 60%, %luminance%, 0.2)',
STAR_COUNT : 100,
init : function(){
this.setParameters();
this.reconstructMethod();
this.createTwigs();
this.createStars();
this.render();
},
setParameters : function(){
this.$container = $('#jsi-fireworks-container');
this.width = this.$container.width();
this.height = this.$container.height();
this.distance = Math.sqrt(Math.pow(this.width / 2, 2) + Math.pow(this.height / 2, 2));
this.contextFireworks = $('<canvas />').attr({width : this.width, height : this.height}).appendTo(this.$container).get(0).getContext('2d');
this.contextTwigs = $('<canvas />').attr({width : this.width, height : this.height}).appendTo(this.$container).get(0).getContext('2d');
this.twigs = [];
this.leaves = [new LEAF(this.width, this.height, this)];
this.stars = [];
this.fireworks = [new FIREWORK(this.width, this.height, this)];
this.leafInterval = this.getRandomValue(this.LEAF_INTERVAL_RANGE) | 0;
this.maxFireworkInterval = this.getRandomValue(this.FIREWORK_INTERVAL_RANGE) | 0;
this.fireworkInterval = this.maxFireworkInterval;
},
reconstructMethod : function(){
this.render = this.render.bind(this);
},
getRandomValue : function(range){
return range.min + (range.max - range.min) * Math.random();
},
createTwigs : function(){
this.twigs.push(new TWIG(this.width, this.height, 0, 0, Math.PI * 3 / 4, 0));
this.twigs.push(new TWIG(this.width, this.height, this.width, 0, -Math.PI * 3 / 4, Math.PI));
this.twigs.push(new TWIG(this.width, this.height, 0, this.height, Math.PI / 4, Math.PI));
this.twigs.push(new TWIG(this.width, this.height, this.width, this.height, -Math.PI / 4, 0));
},
createStars : function(){
for(var i = 0, length = this.STAR_COUNT; i < length; i++){
this.stars.push(new STAR(this.width, this.height, this.contextTwigs, this));
}
},
render : function(){
requestAnimationFrame(this.render);
var maxOpacity = 0,
contextTwigs = this.contextTwigs,
contextFireworks = this.contextFireworks;
for(var i = this.fireworks.length - 1; i >= 0; i--){
maxOpacity = Math.max(maxOpacity, this.fireworks[i].getOpacity());
}
contextTwigs.clearRect(0, 0, this.width, this.height);
contextFireworks.fillStyle = this.SKY_COLOR.replace('%luminance', 5 + maxOpacity * 15);
contextFireworks.fillRect(0, 0, this.width, this.height);
for(var i = this.fireworks.length - 1; i >= 0; i--){
if(!this.fireworks[i].render(contextFireworks)){
this.fireworks.splice(i, 1);
}
}
for(var i = this.stars.length - 1; i >= 0; i--){
this.stars[i].render(contextTwigs);
}
for(var i = this.twigs.length - 1; i >= 0; i--){
this.twigs[i].render(contextTwigs);
}
for(var i = this.leaves.length - 1; i >= 0; i--){
if(!this.leaves[i].render(contextTwigs)){
this.leaves.splice(i, 1);
}
}
if(--this.leafInterval == 0){
this.leaves.push(new LEAF(this.width, this.height, this));
this.leafInterval = this.getRandomValue(this.LEAF_INTERVAL_RANGE) | 0;
}
if(--this.fireworkInterval == 0){
this.fireworks.push(new FIREWORK(this.width, this.height, this));
this.maxFireworkInterval = this.getRandomValue(this.FIREWORK_INTERVAL_RANGE) | 0;
this.fireworkInterval = this.maxFireworkInterval;
}
}
};
🧀 三、可点击的烟花
演示地址:http://haiyong.site/fireworks3
HTML 代码:
<canvas id="canvas"></canvas>
CSS 代码
body{
background-color: #000;
}
canvas{
display: block;
margin: auto;
-webkit-tap-highlight-color:rgba(0,0,0,0);
-webkit-user-select:none;
}
完整 JS 代码
(function() {
var Fireworks, GRAVITY, K, SPEED, ToRadian, canvas, context, ctx, fireBoss, repeat, stage;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
stage = new createjs.Stage(canvas);
stage.autoClear = false;
ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(0, 0, 0, 0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
createjs.Ticker.setFPS(50);
createjs.Touch.enable(stage);
stage.update();
// 重力
GRAVITY = 1;
// 抵抗
K = 0.9;
// 速度
SPEED = 12;
// 从度数转换为弧度
ToRadian = function(degree) {
return degree * Math.PI / 180.0;
};
// 制作烟花的class
Fireworks = class Fireworks {
constructor(sx = 100, sy = 100, particles = 70) {
var circle, i, j, rad, ref, speed;
this.sx = sx;
this.sy = sy;
this.particles = particles;
this.sky = new createjs.Container();
this.r = 0;
this.h = Math.random() * 360 | 0;
this.s = 100;
this.l = 50;
this.size = 3;
for (i = j = 0, ref = this.particles; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {
speed = Math.random() * 12 + 2;
circle = new createjs.Shape();
circle.graphics.f(`hsla(${this.h}, ${this.s}%, ${this.l}%, 1)`).dc(0, 0, this.size);
circle.snapToPixel = true;
circle.compositeOperation = "lighter";
rad = ToRadian(Math.random() * 360 | 0);
circle.set({
x: this.sx,
y: this.sy,
vx: Math.cos(rad) * speed,
vy: Math.sin(rad) * speed,
rad: rad
});
this.sky.addChild(circle);
}
stage.addChild(this.sky);
}
explode() {
var circle, j, p, ref;
if (this.sky) {
++this.h;
for (p = j = 0, ref = this.sky.getNumChildren(); (0 <= ref ? j < ref : j > ref); p = 0 <= ref ? ++j : --j) {
circle = this.sky.getChildAt(p);
// 加速度
circle.vx = circle.vx * K;
circle.vy = circle.vy * K;
// 位置计算
circle.x += circle.vx;
circle.y += circle.vy + GRAVITY;
this.l = Math.random() * 100;
// 粒度
this.size = this.size - 0.001;
if (this.size > 0) {
circle.graphics.c().f(`hsla(${this.h}, 100%, ${this.l}%, 1)`).dc(0, 0, this.size);
}
}
if (this.sky.alpha > 0.1) {
this.sky.alpha -= K / 50;
} else {
stage.removeChild(this.sky);
this.sky = null;
}
} else {
}
}
};
fireBoss = [];
setInterval(function() {
ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}, 40);
setInterval(function() {
var x, y;
x = Math.random() * canvas.width | 0;
y = Math.random() * canvas.height | 0;
fireBoss.push(new Fireworks(x, y));
return fireBoss.push(new Fireworks(x, y));
}, 1300);
repeat = function() {
var fireworks, j, ref;
for (fireworks = j = 0, ref = fireBoss.length; (0 <= ref ? j < ref : j > ref); fireworks = 0 <= ref ? ++j : --j) {
if (fireBoss[fireworks].sky) {
fireBoss[fireworks].explode();
}
}
stage.update();
};
createjs.Ticker.on("tick", repeat);
stage.addEventListener("stagemousedown", function() {
fireBoss.push(new Fireworks(stage.mouseX, stage.mouseY));
return fireBoss.push(new Fireworks(stage.mouseX, stage.mouseY));
});
}).call(this);
🍖 四、3D 旋转烟花
演示地址:http://haiyong.site/fireworks4
HTML 代码:
<canvas id="canvas"></canvas>
CSS 代码
html,body{
margin:0px;
width:100%;
height:100%;
overflow:hidden;
background:#000;
}
#canvas{
width:100%;
height:100%;
}
部分 JS 代码
JS 代码比较长我就不全列出来了,需要完整源码可以翻到文章末尾下载本篇文章所有源码
function initVars(){
pi=Math.PI;
ctx=canvas.getContext("2d");
canvas.width=canvas.clientWidth;
canvas.height=canvas.clientHeight;
cx=canvas.width/2;
cy=canvas.height/2;
playerZ=-25;
playerX=playerY=playerVX=playerVY=playerVZ=pitch=yaw=pitchV=yawV=0;
scale=600;
seedTimer=0;seedInterval=5,seedLife=100;gravity=.02;
seeds=new Array();
sparkPics=new Array();
s="https://cantelope.org/NYE/";
for(i=1;i<=10;++i){
sparkPic=new Image();
sparkPic.src=s+"spark"+i+".png";
sparkPics.push(sparkPic);
}
sparks=new Array();
pow1=new Audio(s+"pow1.ogg");
pow2=new Audio(s+"pow2.ogg");
pow3=new Audio(s+"pow3.ogg");
pow4=new Audio(s+"pow4.ogg");
frames = 0;
}
function rasterizePoint(x,y,z){
var p,d;
x-=playerX;
y-=playerY;
z-=playerZ;
p=Math.atan2(x,z);
d=Math.sqrt(x*x+z*z);
x=Math.sin(p-yaw)*d;
z=Math.cos(p-yaw)*d;
p=Math.atan2(y,z);
d=Math.sqrt(y*y+z*z);
y=Math.sin(p-pitch)*d;
z=Math.cos(p-pitch)*d;
var rx1=-1000,ry1=1,rx2=1000,ry2=1,rx3=0,ry3=0,rx4=x,ry4=z,uc=(ry4-ry3)*(rx2-rx1)-(rx4-rx3)*(ry2-ry1);
if(!uc) return {x:0,y:0,d:-1};
var ua=((rx4-rx3)*(ry1-ry3)-(ry4-ry3)*(rx1-rx3))/uc;
var ub=((rx2-rx1)*(ry1-ry3)-(ry2-ry1)*(rx1-rx3))/uc;
if(!z)z=.000000001;
if(ua>0&&ua<1&&ub>0&&ub<1){
return {
x:cx+(rx1+ua*(rx2-rx1))*scale,
y:cy+y/z*scale,
d:Math.sqrt(x*x+y*y+z*z)
};
}else{
return {
x:cx+(rx1+ua*(rx2-rx1))*scale,
y:cy+y/z*scale,
d:-1
};
}
}
function spawnSeed(){
seed=new Object();
seed.x=-50+Math.random()*100;
seed.y=25;
seed.z=-50+Math.random()*100;
seed.vx=.1-Math.random()*.2;
seed.vy=-1.5;//*(1+Math.random()/2);
seed.vz=.1-Math.random()*.2;
seed.born=frames;
seeds.push(seed);
}
🍰 五、可拖动视角的自定义烟花
演示地址:http://haiyong.site/fireworks5
HTML 代码:
<div id="WebGL-output"></div>
CSS 代码
body {
margin: 0;
overflow: hidden;
background: -webkit-linear-gradient(0deg, rgb(0, 12, 91), rgb(0, 0, 0));
background: linear-gradient(0deg, rgb(0, 12, 91), rgb(0, 0, 0));
}
部分 JS 代码
JS 代码比较长我就不全列出来了,需要完整源码可以参见文章末尾两种获取源码方式
let scene,
camera,
renderer,
orbitControls,
planeMesh,
canvasTexture,
isAutoLaunch = true;
const gravity = new THREE.Vector3(0, -0.005, 0);
const friction = 0.998;
const noise = new SimplexNoise();
const textureSize = 128.0;
const fireworksInstances = [];
let outputDom;
const getOffsetXYZ = i => {
const offset = 3;
const index = i * offset;
const x = index;
const y = index + 1;
const z = index + 2;
return { x, y, z };
};
const getOffsetRGBA = i => {
const offset = 4;
const index = i * offset;
const r = index;
const g = index + 1;
const b = index + 2;
const a = index + 3;
return { r, g, b, a };
};
const gui = new dat.GUI();
const guiControls = new function () {
this.ParticleSize = 300;
this.AutoLaunch = true;
}();
gui.add(guiControls, 'ParticleSize', 100, 600);
gui.add(guiControls, 'AutoLaunch').onChange(e => {
isAutoLaunch = e;
outputDom.style.cursor = isAutoLaunch ? 'auto' : 'pointer';
});
const getRandomNum = (max = 0, min = 0) => Math.floor(Math.random() * (max + 1 - min)) + min;
const launchFireWorks = () => {
if (fireworksInstances.length > 5) return;
const fw = Math.random() > 8 ? new BasicFIreWorks() : new RichFIreWorks();
fireworksInstances.push(fw);
scene.add(fw.meshGroup);
};
const autoLaunch = () => {
if (!isAutoLaunch) return;
if (Math.random() > 0.7) launchFireWorks();
};
const drawRadialGradation = (ctx, canvasRadius, canvasW, canvasH) => {
ctx.save();
const gradient = ctx.createRadialGradient(canvasRadius, canvasRadius, 0, canvasRadius, canvasRadius, canvasRadius);
gradient.addColorStop(0.0, 'rgba(255,255,255,1.0)');
gradient.addColorStop(0.5, 'rgba(255,255,255,0.5)');
gradient.addColorStop(1.0, 'rgba(255,255,255,0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvasW, canvasH);
ctx.restore();
};
const getTexture = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const diameter = textureSize;
canvas.width = diameter;
canvas.height = diameter;
const canvasRadius = diameter / 2;
drawRadialGradation(ctx, canvasRadius, canvas.width, canvas.height);
const texture = new THREE.Texture(canvas);
texture.type = THREE.FloatType;
texture.needsUpdate = true;
return texture;
};
canvasTexture = getTexture();
const getPointMesh = (num, vels, type) => {
const bufferGeometry = new THREE.BufferGeometry();
const vertices = [];
const velocities = [];
const colors = [];
const adjustSizes = [];
const masses = [];
const colorType = Math.random() > 0.3 ? 'single' : 'multiple';
const singleColor = getRandomNum(100, 20) * 0.01;
const multipleColor = () => getRandomNum(100, 1) * 0.01;
let rgbType;
const rgbTypeDice = Math.random();
if (rgbTypeDice > 0.66) {
rgbType = 'red';
} else if (rgbTypeDice > 0.33) {
rgbType = 'green';
} else {
rgbType = 'blue';
}
for (let i = 0; i < num; i++) {
const pos = new THREE.Vector3(0, 0, 0);
vertices.push(pos.x, pos.y, pos.z);
velocities.push(vels[i].x, vels[i].y, vels[i].z);
if (type === 'seed') {
let size;
if (type === 'trail') {
size = Math.random() * 0.1 + 0.1;
} else {
size = Math.pow(vels[i].y, 2) * 0.04;
}
if (i === 0) size *= 1.1;
adjustSizes.push(size);
masses.push(size * 0.017);
colors.push(1.0, 1.0, 1.0, 1.0);
} else {
const size = getRandomNum(guiControls.ParticleSize, 10) * 0.001;
adjustSizes.push(size);
masses.push(size * 0.017);
if (colorType === 'multiple') {
colors.push(multipleColor(), multipleColor(), multipleColor(), 1.0);
} else {
switch (rgbType) {
case 'red':
colors.push(singleColor, 0.1, 0.1, 1.0);
break;
case 'green':
colors.push(0.1, singleColor, 0.1, 1.0);
break;
case 'blue':
colors.push(0.1, 0.1, singleColor, 1.0);
break;
default:
colors.push(singleColor, 0.1, 0.1, 1.0);}
}
}
}
bufferGeometry.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3).setDynamic(true));
bufferGeometry.addAttribute('velocity', new THREE.Float32BufferAttribute(velocities, 3).setDynamic(true));
bufferGeometry.addAttribute('color', new THREE.Float32BufferAttribute(colors, 4).setDynamic(true));
bufferGeometry.addAttribute('adjustSize', new THREE.Float32BufferAttribute(adjustSizes, 1).setDynamic(true));
bufferGeometry.addAttribute('mass', new THREE.Float32BufferAttribute(masses, 1).setDynamic(true));
✨ 完整源码下载
考虑到有些小伙伴基础比较薄弱,本篇文章所有源码都已打包好,下载方式有如下三种
1.关注公众号【海拥】回复【烟花】获取
2.通过我的主页获取联系方式:https://www.infoq.cn/u/haiyong,记得备注 InfoQ
版权声明: 本文为 InfoQ 作者【海拥(haiyong.site)】的原创文章。
原文链接:【http://xie.infoq.cn/article/1928c13a01353e62fcee005b3】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
公众号:海拥 2021.11.29 加入
【个人网站】haiyong.site 【联系方式】微信:wh18363 【软件技能】前端,Java,Python 【个人称号】InfoQ写作平台签约作者,华为云享专家,CSDN原力作者,全栈领域优质创作者,掘金2021年度人气作者No.21
评论