程式設計師的小浪漫----煙火

王興欣發表於2018-02-22

多程式碼,慎讀!!!

預覽

程式設計師的小浪漫----煙火

完整專案預覽----預覽地址;

屬性設計

煙花狀態:煙花應有三個狀態:

  1. 升空
  2. 等待炸裂
  3. 炸裂後

煙花:發射點(x, y),爆炸點(xEnd, yEnd),升空後等待炸裂時間(wait),炸裂後微粒個數(count),煙花半徑(radius)

煙花炸裂後微粒:自身位置(x, y),自身大小(size),自身速度(rate),最大煙花半徑(radius)。

config:為全域性變數,以及控制引數,包括畫布寬高,設定煙花屬性等。

設定全域性變數

const config = {
    width: 360,
    height: 600,
    canvases: ['bg', 'firework'],
    skyColor: '210, 60%, 5%, 0.2)',
    fireworkTime:{min:30,max:60},
    //煙花引數本身有預設值 傳入undefined則使用預設引數
    fireworkOpt:{
    	x: undefined,
    	y: undefined,
    	xEnd: undefined,
    	yEnd: undefined,
    	count: 300,   //炸裂後粒子數
    	wait: undefined,  //消失後 => 炸裂  等待時間
    }
}
複製程式碼

構建微粒類

class Particle{
    //預設值寫法 
    constructor({x, y, size = 1, radius = 1.2} = {}){
    	this.x = x;
    	this.y = y;
    	this.size = size;
        
    	this.rate = Math.random(); //每個微粒移動的速度都是隨機不同的
    	this.angle = Math.PI * 2 * Math.random(); //每個微粒的偏移角度
    	
    	//每次微粒移動速度分解為橫縱座標的移動。
    	this.vx = radius * Math.cos(this.angle) * this.rate; 
    	this.vy = radius * Math.sin(this.angle) * this.rate;
    }

    go(){
    	this.x += this.vx;
    	this.y += this.vy; 
    	this.vy += 0.02; //重力影響 y越大實際越偏下
    	
    	//空氣阻力
    	this.vx *= 0.98;
    	this.vy *= 0.98;
    }
    
    //畫出微粒的位置
    render(ctx){
    	this.go();
    	ctx.beginPath();
    	ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
    	ctx.fill();
    }
}
複製程式碼

構建煙花類

class Firework{
    constructor({x, y = config.height, xEnd, yEnd, count = 300, wait} = {}){
        //煙花自身屬性
    	this.x = x || config.width / 8 + Math.random() * config.width * 3 / 4; 
    	this.y = y;
    	this.xEnd = xEnd || this.x;
    	this.yEnd = yEnd || config.width / 8 + Math.random() * config.width * 3 / 8;
    	this.size = 2;
    	this.velocity = -3;
    	
    	this.opacity = 0.8;
    	this.color = `hsla(${360 * Math.random() | 0},80%,60%,1)`;
    	this.wait = wait || 30 + Math.random() * 30;
    	//微粒個數等
        this.count = count;
    	this.particles = [];
    	this.createParticles();
    
    	this.status = 1;
    }
    //建立微粒
    createParticles(){
    	for(let i = 0;i < this.count; ++i){
            this.particles.push(new Particle({x:this.xEnd, y:this.yEnd}));
    	}
    }
    //升空
    rise(){
    	this.y += this.velocity * 1;
    	this.velocity += 0.005; //升空時產生的阻力
    	//煙花升空到目標位置開始漸隱
    	if(this.y - this.yEnd <= 50){
    		this.opacity = (this.y - this.yEnd) / 50;
    	}
    	//如果到了目標位置 就開始第二個狀態
    	if(this.y <= this.yEnd){
    		this.status = 2;
    	}
    }
    
    //渲染煙花  煙花所有動作完成之後返回false
    render(ctx){
        switch(this.status){
            case 1: //升空
    		ctx.save();
    		ctx.beginPath();
    		ctx.globalCompositeOperation = 'lighter';
    		ctx.globalAlpha = this.opacity;
    		ctx.translate(this.x, this.y);
    		ctx.scale(0.8, 2.3);
    		ctx.translate(-this.x, -this.y);
    		ctx.fillStyle = this.color;
    		ctx.arc(this.x + Math.sin(Math.PI * 2 * Math.random()) / 1.2, this.y, this.size, 0, Math.PI * 2, false);
    		ctx.fill();
    		ctx.restore();
    
    		this.rise();
    		return true;
            break;
    	    case 2: //煙花消失階段,等待炸裂
    		if(--this.wait <= 0){
    		    this.opacity = 1;
    		    this.status = 3;
    		}
    		return true;
    	    break;
    	    case 3: //炸裂之後 渲染煙花微粒
    		ctx.save();
    		ctx.globalCompositeOperation = 'lighter';
    		ctx.globalAlpha = this.opacity;
    		ctx.fillStyle = this.color;
    		for(let i = 0;i < this.particles.length;++i){
		    this.particles[i].render(ctx);
    		}
    		ctx.restore();
    		this.opacity -= 0.01;
    		return this.opacity > 0;
    	    break;
    	    default: 
    		return false;
        }
    }
}
複製程式碼

放煙花

const canvas = {
    init: function(){
        //一些屬性的設定 可以不用管
    	this.setProperty();
    	this.renderBg();
    	
    	//迴圈體 **主要
    	this.loop();
    },
    setProperty: function(){
    	this.fireworks = [];
    	this.width = config.width;
    	this.height = config.height;
    	this.fireworkTime = (config.fireworkTime.min + (config.fireworkTime.max - config.fireworkTime.min) * Math.random()) | 0;
    
    	this.bgCtx = document.querySelector('#bg').getContext('2d');
    	this.fireworkCtx = document.querySelector('#firework').getContext('2d');
    },
    renderBg(){
    	this.bgCtx.fillStyle = 'hsla(210, 60%, 5%, 0.9)'
    	this.bgCtx.fillRect(0, 0, this.width, this.height);
    },
    
    loop(){
    	requestAnimationFrame(this.loop.bind(this));
    	this.fireworkCtx.clearRect(0, 0, this.width, this.height);
        
        //隨機建立煙花
    	if(--this.fireworkTime <= 0){
            this.fireworks.push(new Firework(config.fireworkOpt));
            //每次到點之後重新設定煙花產生時間 (|0轉化為整數)
            this.fireworkTime = (config.fireworkTime.min + (config.fireworkTime.max - config.fireworkTime.min) * Math.random()) | 0;
    	}
    
    	for(let i = this.fireworks.length - 1; i >= 0; --i){
    	    //渲染煙花 (若返回值為false則移除煙花)
            !this.fireworks[i].render(this.fireworkCtx) && this.fireworks.splice(i,1);	
    	}
    
    }
}
canvas.init();
複製程式碼

完善

程式設計師的小浪漫----煙火

此時煙花是這樣的,感覺少了點小尾巴。

現在我們每一幀都是清除了畫布,如果要加上小尾巴其實也很簡單,每一幀都不要清除畫布,而是覆蓋一層新的有透明度的天空上去。

//canvas.loop方法

// this.fireworkCtx.clearRect(0, 0, this.width, this.height);
this.fireworkCtx.fillStyle = config.skyColor;
this.fireworkCtx.fillRect(0,0,this.width,this.height);	
複製程式碼

程式設計師的小浪漫----煙火

這時就變成這樣了。

但是,還是缺少了在爆炸瞬間 天空變亮的場景。

那麼在畫煙花的時候,先會獲取一下煙花的顏色以及透明度。

// *****Firework constructor
// this.color = `hsla(${360 * Math.random() | 0},80%,60%,1)`;
this.hue = 360 * Math.random() | 0;
this.color = `hsla(${this.hue},80%,60%,1)`;
複製程式碼
// *****Firework 新增例項方法
getSkyColor(){
    const skyColor = {
        //只有炸裂階段才返回亮度
    	lightness: this.status == 3 ? this.opacity : 0 ,
    	hue: this.hue
    };
    return skyColor;
}
複製程式碼
// *****config 修改config的skyColor
// skyColor: 'hsla(210, 60%, 5%, 0.2)',
skyColor: 'hsla({hue}, 60%, {lightness}%, 0.2)',
複製程式碼
// canvas.loop方法
//this.fireworkCtx.fillStyle = config.skyColor;
//每次替換色調與亮度值。
this.fireworkCtx.fillStyle = config.skyColor.replace('{lightness}', 5 + this.skyColor.lightness * 15).replace('{hue}' , this.skyColor.hue);

this.skyColor = { //新增
    lightness: 0,
    hue: 210
};
for(let i = this.fireworks.length - 1; i >= 0; --i){
    //新增 天空顏色為最亮的煙花的顏色
    this.skyColor = this.skyColor.lightness >= this.fireworks[i].getSkyColor().lightness ? this.skyColor : this.fireworks[i].getSkyColor();
    !this.fireworks[i].render(this.fireworkCtx) && this.fireworks.splice(i,1);	
}
複製程式碼

程式設計師的小浪漫----煙火

到現在就算是大功告成了。

完整專案

github專案地址

如果覺得還不錯,請star一個吧。

煙花製作參考連結

參考了codepen.io上的很多作品。

主要參考 --- fireworks seen in the countryside

fireworks seen in the countryside

相關文章