使用canvas制作烟花

479 阅读2分钟

春节期间上海市区禁放烟花,今天又正好是迎财神的日子,我们可以试着用 js 来实现烟花的效果,希望大家来年顺顺利利。

firework.gif

项目创建

先创建一个整屏幕的画布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)
  


})()

如下图所示 image.png

烟花的基础

烟花从一个点爆炸散开,所以可以先绘制几个围绕圆心的小圆点,作为最初始的状态。

     //最基础的烟花
    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();
        }
    }

如下图所示

image.png

让粒子运动

我们先让上述的粒子运动起来,这里需要一个数组来管理这些粒子,同时每个粒子要做成一个对象存放在这个数组中。再做一个定时器,每一帧更新粒子的状态并在画布上绘制。

    //烟花粒子创建
    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()

运动粒子.gif

好像已经有一点烟花散开的感觉了,真实烟花运动轨迹还需要更随机一些

运动轨迹增强

    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();
       }

    }

如下图所示 运动粒子3.gif

最后我们再加上拖尾效果

//在定时器里增加如下代码
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)
    }

这样就大功告成了,感兴趣可以动手试一试,给节日增加一点乐趣。