春节期间上海市区禁放烟花,今天又正好是迎财神的日子,我们可以试着用 js 来实现烟花的效果,希望大家来年顺顺利利。
项目创建
先创建一个整屏幕的画布canvas。
!(function(){
//创建 canvas
const canvas=document.createElement('canvas');
document.body.appendChild(canvas)
//获取上下文
const context=canvas.getContext('2d')
function resizeCanvas(){
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;
}
function clearCanvas(){
context.fillStyle='#000000';
context.fillRect(0,0,canvas.width,canvas.height);
}
function renderCanvas(){
resizeCanvas()
clearCanvas()
}
//监听 resize事件,canvas始终整屏。
window.addEventListener("resize",renderCanvas)
})()
如下图所示
烟花的基础
烟花从一个点爆炸散开,所以可以先绘制几个围绕圆心的小圆点,作为最初始的状态。
//最基础的烟花
function mouseDownHandler(e){
let x=e.clientX;
let y=e.clientY;
console.log(x,y)
createFireworks(x,y)
}
//监听点击事件
document.addEventListener("mousedown",mouseDownHandler)
function createFireworks(x,y){
const count=10;
const radius=10;
for(let i=0;i<count;i++){
const angle=360/count*i; //角度
const radians=angle*Math.PI/180 //对应的弧度
//计算小圆点坐标
const sx=x+Math.cos(radians)*radius
const sy=y+Math.sin(radians)*radius
context.beginPath();
context.arc(sx,sy,2,0,Math.PI*2,false)
context.closePath()
context.fillStyle='#ff0000'
context.fill();
}
}
如下图所示
让粒子运动
我们先让上述的粒子运动起来,这里需要一个数组来管理这些粒子,同时每个粒子要做成一个对象存放在这个数组中。再做一个定时器,每一帧更新粒子的状态并在画布上绘制。
//烟花粒子创建
const particles=[] //烟花粒子管理器
function createFireworks(x,y){
const count=10;
const radius=0;
for(let i=0;i<count;i++){
const angle=360/count*i;
const radians=angle*Math.PI/180
//每个粒子状态
const p={};
p.sx=x;
p.sy=y;
p.radians=radians
p.radius=radius
p.size=2
particles.push(p)
}
}
//定时渲染画布,先更新粒子状态,再画粒子
function drawFireworks(){
clearCanvas();
for(let i=0;i<particles.length;i++){
const p=particles[i]
p.vx=p.sx+Math.cos(p.radians)*p.radius
p.vy=p.sy+Math.sin(p.radians)*p.radius
//扩大半径
p.radius+=1
//如果超出屏幕就从管理器中删除
if(p.vx<0||p.vx>canvas.width||p.vy<0||p.vy>canvas.height){
particles.splice(i,1)
continue
}
context.beginPath();
context.arc(p.vx,p.vy,p.size,0,Math.PI*2,false)
context.closePath()
context.fillStyle='#ff0000'
context.fill();
}
}
//定时器动函数,这里用 requestAnimationFrame来做定时器
function tick(){
drawFireworks()
requestAnimationFrame(tick)
}
//启动定时器
tick()
好像已经有一点烟花散开的感觉了,真实烟花运动轨迹还需要更随机一些
运动轨迹增强
function createFireworks(x,y){
const count=100;
const radius=0;
let hue=Math.floor(Math.random()*51)+150
let hueVar=30
for(let i=0;i<count;i++){
const angle=360/count*i;
const radians=angle*Math.PI/180
const p={};
p.x=x;
p.y=y;
p.radians=radians
p.size=2
p.hue=Math.floor(Math.random()*((hue+hueVar)-(hue-hueVar)))+(hue-hueVar)//色度
p.brightness=Math.floor(Math.random()*31)+50 //亮度
p.alpha=(Math.floor(Math.random()*61)+40)/100 //透明度
//每个粒子拥有不一样的初速度
p.speed=(Math.random()*5)+.4;
//粒子初始半径
p.radius=p.speed
particles.push(p)
}
console.log(particles)
}
function drawFireworks(){
clearCanvas();
for(let i=0;i<particles.length;i++){
const p=particles[i]
const vx=Math.cos(p.radians)*p.radius
const vy=Math.sin(p.radians)*p.radius+0.4
p.x+=vx
p.y+=vy
// p.radius+=1
// 半径会逐渐扩大,但扩散速度越来越缓慢
p.radius*=1-p.speed/100
//透明度逐渐降低,最后消失
p.alpha-=0.005;
//看不见粒子了就将其删除
if(p.alpha<=0){
particles.splice(i,1)
continue
}
context.beginPath();
context.arc(p.x,p.y,p.size,0,Math.PI*2,false)
context.closePath()
//将明暗,色度,透明度都画上
context.fillStyle=`hsla(${p.hue},100%,${p.brightness}%,${p.alpha})`
context.fill();
}
}
如下图所示
最后我们再加上拖尾效果
//在定时器里增加如下代码
function tick(){
context.globalCompositeOperation='destination-out';
context.fillStyle=`rgba(0,0,0,0.1)`
context.fillRect(0,0,canvas.width,canvas.height)
context.globalCompositeOperation='lighter'
drawFireworks()
requestAnimationFrame(tick)
}
这样就大功告成了,感兴趣可以动手试一试,给节日增加一点乐趣。