原文地址:canvas繪制太陽(yáng)系
學(xué)習(xí)canvas有一段時(shí)間了惠呼,順便寫(xiě)個(gè)小項(xiàng)目練手截碴,該項(xiàng)目用到的知識(shí)點(diǎn)包括:
- ES6面向?qū)ο?/li>
- 基本的三角函數(shù)
- canvas部分有:坐標(biāo)變換旭绒,漸變糠悯,混合模式洋满,線條和圖形的繪制孝赫。
實(shí)際效果: solar system
場(chǎng)景
首先建立場(chǎng)景類曼振,主要用來(lái)組織管理對(duì)象几迄,統(tǒng)一更新和繪制對(duì)象。這里用到了ES6的類語(yǔ)法冰评,構(gòu)造函數(shù)建立對(duì)象列表屬性planets乓旗,繪制背景方法drawBG,使用requestAnimationFrame反復(fù)執(zhí)行的動(dòng)畫(huà)方法animate
繪制背景使用到了徑向漸變:createRadialGradient(x1,y1,r1,x2,y2,r2); 該漸變主要用于創(chuàng)建兩個(gè)圓相交過(guò)渡效果集索,如果前后兩個(gè)圓心相同(x1==x2 && y1==y2)屿愚,則會(huì)構(gòu)造同心圓樣式的漸變。 這樣我們就以太陽(yáng)為中心的黃色調(diào)漸變到黑色务荆,最后用fillRect填充整個(gè)背景妆距。
//場(chǎng)景
class Stage {
constructor(){
this.planets=[];
}
init(ctx){
ctx.translate(W/2,H/2);//坐標(biāo)重置為中間
this.animate(ctx);
}
//繪制背景
drawBG(ctx){
ctx.save();
ctx.globalCompositeOperation = "source-over";
var gradient=ctx.createRadialGradient(0,0,0,0,0,600);
gradient.addColorStop(0,'rgba(3,12,13,0.1)');
gradient.addColorStop(1,'rgba(0,0,0,1');
ctx.fillStyle=gradient;
// ctx.fillStyle='rgba(0,0,0,0.9)';
ctx.fillRect(-W/2,-H/2,W,H);
ctx.restore();
}
//執(zhí)行動(dòng)畫(huà)
animate(ctx){
var that=this,
startTime=new Date();
(function run(){
that.drawBG(ctx);
that.planets.forEach(item=>{
item.update(startTime);
item.draw(ctx);
});
requestAnimationFrame(run);
}());
}
}
星球
然后建立星球基類,除構(gòu)造函數(shù)函匕,還有更新位置角度的方法Update娱据,對(duì)象繪制方法draw。之后所有的星球盅惜,都會(huì)初始化該類或者繼承該類建立對(duì)應(yīng)星球中剩。
行星繞太陽(yáng)做圓周運(yùn)動(dòng),這個(gè)可以用三角函數(shù)根據(jù)角度和半徑求出x,y抒寂,但還有更加方便的方法结啼,那就是使用canvas提供的坐標(biāo)旋轉(zhuǎn)方法rotate,以360度為一個(gè)周期屈芜。
/**
* 星球基類
*/
class Planet{
/**
* @param {Number} x x坐標(biāo)
* @param {Number} y y坐標(biāo)
* @param {Number} r 半徑
* @param {Number} duration 周期(秒)
* @param {Object} fillStyle
* @param {Object} blurStyle
*/
constructor(x,y,r,duration,fillStyle,blurStyle){
this.x=x;
this.y=y;
this.r=r;
this.duration=duration;
this.angle=0;
this.fillStyle=fillStyle;
this.blurStyle=blurStyle;
}
update(startTime){
this.angle=Tween.linear(new Date()-startTime,0,Math.PI*2,this.duration*1000);
}
draw(ctx){
ctx.save();
ctx.rotate(this.angle);
// ctx.translate(this.x,this.y);
drawCircle(this.x,this.blurStyle.color);
ctx.beginPath();
// ctx.globalCompositeOperation = "lighter";
ctx.fillStyle=this.fillStyle;
ctx.shadowColor=this.blurStyle.color;
ctx.shadowBlur=this.blurStyle.blur;
// ctx.arc(0,0,this.r,Math.PI*2,false);
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.fill();
ctx.restore();
}
};
太陽(yáng)
開(kāi)始建立第一個(gè)對(duì)象-太陽(yáng)郊愧,繼承上面的星球基類Planet朴译,重寫(xiě)draw方法
/**
* 太陽(yáng)
*/
class Sun extends Planet{
draw(ctx){
ctx.save();
ctx.beginPath();
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle=this.fillStyle;
ctx.shadowColor=this.blurStyle.color;
ctx.shadowBlur=this.blurStyle.blur;
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.fill();
ctx.restore();
}
}
土星
土星有美麗的土星環(huán),所以也繼承出一個(gè)單獨(dú)的類属铁,重寫(xiě)draw方法眠寿,其中土星環(huán)比較麻煩,建立了很多顏色節(jié)點(diǎn)的徑向漸變焦蘑。
/**
* 土星
*/
class Saturn extends Planet{
draw(ctx){
ctx.save();
ctx.rotate(this.angle);
drawCircle(this.x,this.blurStyle.color);
ctx.beginPath();
ctx.fillStyle=this.fillStyle;
ctx.arc(this.x,this.y,this.r,Math.PI*2,false);
ctx.fill();
//土星光環(huán)
ctx.globalCompositeOperation = "source-over";
var gradient=ctx.createRadialGradient(this.x,this.y,0,this.x,this.y,this.r+25);
var startStop=(this.r+3)/(this.r+24);
gradient.addColorStop(startStop,'#282421');
gradient.addColorStop(startStop+0.06,'#282421');
gradient.addColorStop(startStop+0.1,'#7e7966');
gradient.addColorStop(startStop+0.18,'#706756');
gradient.addColorStop(startStop+0.24,'#7e7966');
gradient.addColorStop(startStop+0.25,'#282421');
gradient.addColorStop(startStop+0.26,'#282421');
gradient.addColorStop(startStop+0.27,'#807766');
gradient.addColorStop(1,'#595345');
ctx.fillStyle=gradient;
ctx.beginPath();
ctx.arc(this.x,this.y,this.r+24,0,Math.PI*2,true);
ctx.arc(this.x,this.y,this.r+3,0,Math.PI*2,false);
ctx.fill();
ctx.restore();
}
}
建立星球
接著開(kāi)始初始化星球?qū)ο蠖⒐埃ㄌ?yáng)和八大行星,然后所有的星球顏色都使用了徑向漸變例嘱,這樣更加的美觀坟乾。這里給出太陽(yáng),水星蝶防,土星的例子甚侣,其他的行星如此類推。
// 初始化場(chǎng)景類
var stage=new Stage();
// sun
var sunStyle=ctx.createRadialGradient(0,0,0,0,0,60);
sunStyle.addColorStop(0,'white');
sunStyle.addColorStop(0.5,'white');
sunStyle.addColorStop(0.8,'#ffca1e');
sunStyle.addColorStop(1,'#b4421d');
var sun=new Sun(0,0,60,0,sunStyle,{color:'#b4421d',blur:300});
stage.planets.push(sun);
// mercury
var mercuryStyle=ctx.createRadialGradient(100,0,0,100,0,9);
mercuryStyle.addColorStop(0,'#75705a');
mercuryStyle.addColorStop(1,'#464646');
var mercury=new Planet(100,0,9,8.77,mercuryStyle,{color:'#464646'});
stage.planets.push(mercury);
//saturn
var saturnStyle=ctx.createRadialGradient(500,0,0,500,0,26);
saturnStyle.addColorStop(0,'#f2e558');
saturnStyle.addColorStop(1,'#4c4a3b');
var saturn =new Saturn(500,0,26,1075.995,saturnStyle,{color:'#4c4a3b'});
stage.planets.push(saturn);
小行星帶
當(dāng)然還有火星和木星之間的小行星帶间学,同理繼承星球基類殷费,這里用到了圖像混合模式globalCompositeOperation,使用xor可以和背景對(duì)比度沒(méi)那么突兀低葫。當(dāng)然還有其他屬性值详羡,比如source-over, lighter等。這里我們隨機(jī)生成了300個(gè)對(duì)象嘿悬,一樣填充進(jìn)場(chǎng)景類的planets屬性統(tǒng)一更新繪制实柠。
/**
* 小行星
*/
class Asteroid extends Planet{
draw(ctx){
ctx.save();
ctx.rotate(this.angle);
ctx.beginPath();
ctx.globalCompositeOperation = "xor";
ctx.fillStyle=this.fillStyle;
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.fill();
ctx.restore();
}
}
function createAsteroids(){
var asteroid=null,
x=300,y=0, r=2,rd=300,
angle=0, d=283,
color='#fff';
for(var i=0;i<400;i++){
rd=Random(300,320);
angle=Random(0,Math.PI*2*1000)/1000;
x=Math.round(Math.cos(angle)*rd);
y=Math.round(Math.sin(angle)*rd);
r=Random(1,3);
d=Random(28.3,511);
color=getAsteroidColor();
// console.log(angle,color);
asteroid = new Asteroid(x,y,r,d,color,{color:color,blur:1});
stage.planets.push(asteroid);
}
}
彗星
基本快完成了,但我們除此之外善涨,可以再添加做橢圓運(yùn)動(dòng)的彗星窒盐,這樣更加酷。一樣隨機(jī)生成20個(gè)彗星填充進(jìn)場(chǎng)景類統(tǒng)一更新繪制钢拧。
/**
* 彗星
*/
class Comet {
constructor(cx,cy,a,b,r,angle,color,duration){
this.a=a;
this.b=b;
this.r=r;
this.cx=cx;
this.cy=cy;
this.x=0;
this.y=0;
this.color=color;
this.angle=angle;
this.duration=duration;
}
update(startTime){
var t=Tween.linear(new Date()-startTime,0,Math.PI*2,this.duration*1000);
this.x=this.cx+this.a*Math.cos(this.angle+t);
this.y=this.cy+this.b*Math.sin(this.angle+t);
}
draw(){
ctx.save();
ctx.rotate(this.angle);
//畫(huà)運(yùn)動(dòng)軌跡
ctx.lineWidth=0.5;
ctx.strokeStyle='rgba(15,69,116,0.2)';
Shape.ellipse(ctx,this.cx,this.cy,this.a,this.b);
//畫(huà)球
ctx.beginPath();
// ctx.globalCompositeOperation = "lighter";
ctx.globalCompositeOperation = "source-atop";
ctx.shadowColor=this.color;
ctx.shadowBlur=1;
ctx.fillStyle=this.color;
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.fill();
//畫(huà)尾跡
ctx.restore();
}
}
function createComets(){
var l=180,
a=800,b=300,
cx=a-l,cy=0,
r=3,duration=30,angle=0,color='#fff',
comet = null;
for(var i=0;i<20;i++){
l=Random(120,350)
a=Random(600,1000);
b=a/Random(1,3);
cx=a-l;
r=Random(2,4);
angle=Random(0,Math.PI*2*1000)/1000;
color=getCometColor();
duration=Random(20,100);
stage.planets.push(new Comet(cx,cy,a,b,r,angle,color,duration));
}
}
運(yùn)動(dòng)軌跡
最后的細(xì)節(jié)蟹漓,就是標(biāo)識(shí)出行星圓周運(yùn)動(dòng)的軌道,當(dāng)然最簡(jiǎn)單的是按運(yùn)動(dòng)半徑畫(huà)個(gè)圓源内。但我們用線性漸變添加好看的尾跡葡粒,這樣效果更好
function drawCircle(r,color){
var hsl=Color.hexToHsl(color);
ctx.lineWidth=1;
// ctx.strokeStyle='rgba(176,184,203,0.3)';
// ctx.arc(0,0,this.x,Math.PI*2,false);
// ctx.stroke();
var gradient=ctx.createLinearGradient(-r,0,r,0);
gradient.addColorStop(0,'hsla('+hsl[0]+','+hsl[1]+'%,0%,.3)');
gradient.addColorStop(0.6,'hsla('+hsl[0]+','+hsl[1]+'%,50%,.9)');
gradient.addColorStop(1,'hsla('+hsl[0]+','+hsl[1]+'%,80%,1)');
ctx.strokeStyle=gradient;
// ctx.shadowColor=color;
// ctx.shadowBlur=4;
ctx.beginPath();
ctx.arc(0,0,r,0,Math.PI,true);
ctx.stroke();
}
最后
所有的部分都已經(jīng)完成,我們只需要啟動(dòng)場(chǎng)景類即可
createAsteroids();
createComets();
stage.init(ctx);