1. 程式人生 > >canvas初學之——繪製一片星空

canvas初學之——繪製一片星空

                                       利用canvas畫一片星空

效果圖如下:

觀察這篇星空:
    多一半的位置被隨機分佈的星星所覆蓋,右上角有一輪月牙,背景色為深藍色到黑色的漸變色,下方是一片波浪形綠地,綠地顏色也為漸變色。


1.首先,如何繪製漸變色?
   已知可以繪製的漸變色有兩種情況,徑向漸變(Radial Gradient)和線性漸變(Linear Gradient)。

                                                              徑向漸變與線性漸變效果如圖:

第一張圖為線性漸變,第二張圖為徑向漸變

·線性漸變Linear Gradient
            eg:var grd = cxt.createLinearGradient(xstart,ystart,xend,yend);
                //構成漸變線,得到漸變線的方向和尺度
               grd.addColorStop(stop,color);
                stop:介於 0.0 與 1.0 之間的值,表示漸變中開始與結束之間的位置
                color 在結束位置顯示的 CSS 顏色值     

    上圖中線性漸變程式碼如下:


			var linearGrad = context1.createLinearGradient(0,0,800,800);
			linearGrad.addColorStop(0.0, '#fff');
			linearGrad.addColorStop(0.3, 'yellow');
			linearGrad.addColorStop(0.6, '#e89abe');
			linearGrad.addColorStop(1.0, 'blue');
			context1.fillStyle = linearGrad;
			context1.fillRect(0,0,800,800);

在0.0-1.0種用了4種顏色,這4種顏色 呈線性漸變


 ·徑向漸變Radial Gradient
            呈放射狀漸變,定義在兩個同心圓,而非兩點  
            eg:var grd = cxt.createRadialGradient(x0,y0,r0,x1,y1,r1);
               grd.addColorStop(stop,color);

上圖中徑向漸變程式碼如下:

var radialGrad = context2.createRadialGradient(400,400,0,400,400,500);
			radialGrad.addColorStop(0.0, '#fff');
			radialGrad.addColorStop(0.3, 'yellow');
			radialGrad.addColorStop(0.6, '#e89abe');
			radialGrad.addColorStop(1.0, 'blue');
			context2.fillStyle = radialGrad;
			context2.fillRect(0,0,800,800);

在0.0-1.0種用了4種顏色,這4種顏色 呈徑向漸變

而在星空例子中徑向漸變效果更好;
  徑向漸變呈放射狀漸變,是定義在兩個同心圓之間的
  通過createRadialGradient(x0,y0,r0,x1,y1,r1)函式,其中,x0,y0,r0表示小圓
  原點座標以及半徑,,x1,y1,r1表示大圓原點座標及半徑;
  再利用addColorStop(stop,color)函式,在0.0處定義深藍色,在1.0初定義黑色
  這樣就可以做出放射狀的漸變色了;

2.如何繪製漫天繁星?

首先看單獨的一顆五角星如何繪製:

                                                     (圖片轉自慕課網liuyubobobo講師的課程Canvas繪圖詳解)

  根據觀察,可以得到,五角星的繪製其實是基於兩個同心圓的基礎上的,根據這兩個同心圓,可以算出每個角的座標

程式碼如下:

		function starPath(cxt){//繪製一個標準的五角星
			cxt.beginPath();
			for (var i = 0; i < 5; i++) {
				cxt.lineTo(Math.cos( (18+i*72)/180*Math.PI),
					-Math.sin((18+i*72)/180*Math.PI));

				cxt.lineTo(Math.cos( (54+i*72)/180*Math.PI)*0.5,
					-Math.sin((54+i*72)/180*Math.PI)*0.5);

			}
			cxt.closePath();
		}

這是以大圓半徑為1 來繪製的一個標準的五角星;(我們令小圓半徑為大圓半徑的0.5倍)

其中一個注意點:在Math中的三角函式是以弧度值來計算的,因此在使用sin與cos時,需要將角度轉化為弧度。

這樣用for迴圈遍歷5次,即得到了一個標準的五角星;

我們將這個過程封裝成一個函式starPath(cxt)以便複用,其中引數cxt是繪圖上下文環境

然而夜空中不可能只有一顆星星,而且天上的星星的排列應該是隨機的,因此,我們需要採用Math中的random隨機函式來隨機星星的座標與大小;

for (var i = 0; i < 200; i++) {
				var r = Math.random()*5+5;
				var x = Math.random()*canvas.width;
				var y = Math.random()*canvas.height*0.65;
				var a = Math.random()*360;
				drawStar(cxt,r,x,y,a);
			}

我們隨機了星星的半徑,座標以及旋轉角度,遍歷200次,使天空中出現200顆星星

而其中繪製星星,我們也將其封裝為一個函式drawStar(cxt,r,x,y,a),其中,r表示大圓半徑,(x,y)為星星偏移量,a為星星的旋轉角度

	function drawStar(cxt,R,x,y,rot){//rot表示旋轉角度

			cxt.save();

			cxt.translate(x,y);
			cxt.rotate(rot/180*Math.PI);
			cxt.scale(R,R);

			starPath(cxt)


			cxt.fillStyle = '#fb3';
		


			cxt.fill();

			cxt.restore();

		}

這樣,我們就繪製好了一片星空了 

3.接下來,是繪製一輪月牙 

                                                  (圖片轉自慕課網liuyubobobo講師的課程Canvas繪圖詳解) 

可以看出,這個月牙的外圓是一個1/2圓弧,易畫出,可用arc()函式,然而內圓比較複雜,需要通過計算,再使用arcTo()函式畫出。

實際實現中,為了使程式碼複用方便,我們繪製的是一個以(0,0)點為圓心,1為半徑的一個月牙

function pathMoon(cxt, d){
			cxt.beginPath();
			cxt.arc(0, 0, 1, 0.5*Math.PI, 1.5*Math.PI, true);
			cxt.moveTo(0, -1);
			cxt.arcTo(d, 0, 0, 1, dis(0, -1, d, 0)/d);
			cxt.closePath();
		}

其中,引數d表示控制點,即圖中C點的橫座標

在實現這個函式時,我們還封裝了一個函式dis(x1,y1,x2,y2),用於求取直角三角形的斜邊:

function dis(x1, y1, x2, y2){//直角三角形求取斜邊
			return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

這樣,我們就畫好了一個標準的月亮,而為了使這個月亮的大小位置顏色等屬性滿足我們的星空,還需要 進行完善,我們封裝了一個函式fillMoon(cxt, d, x, y, R, rot, /*optional*/fillColor),其中,d表示控制點,(x,y)表示偏移量,R表示對月亮的縮放倍數,rot表示旋轉角度,還有一個可選引數fillColor,表示月亮的顏色,如果沒有給定,則用預設顏色#fb5

function fillMoon(cxt, d, x, y, R, rot, /*optional*/fillColor){
			cxt.save();
			cxt.translate(x,y);
			cxt.rotate(rot*Math.PI/180);
			cxt.scale(R,R);
			pathMoon(cxt, d):
			cxt.fillStyle = fillColor || '#fb5';
			cxt.fill();
			cxt.restore();
		}

最後呼叫fillMoon函式,這樣,我們的月亮也畫好啦

4.如何繪製波浪形的綠地

我們已知,使用arcTo函式是沒辦法繪製波浪線的,貝塞爾二次曲線也無法繪製波浪線,因為這兩個都只有一個控制點,因此這裡我們只能用貝塞爾三次曲線來繪製波浪線。

貝塞爾三次曲線
        context.moveTo(x0, y0); 起始點
        context.bezierCurveTo(x1, y1, 控制點,
                                             x2, y2, 控制點,
                                             x3, y3); 結束點

與貝塞爾二次曲線不同,貝塞爾三次曲線有6個引數,分為兩個控制點和一個結束點,因此,要繪製這片綠地,需要使用貝塞爾三次曲線。同時,這片綠地的背景色我們採用線性漸變色來處理,程式碼如下:

	function drawLand(cxt){
			cxt.save();
			cxt.beginPath();
			cxt.moveTo(0, 600);
			cxt.bezierCurveTo(540, 400, 600, 800, 1200, 600);
			cxt.lineTo(1200,800);
			cxt.lineTo(0,800);
			cxt.closePath();

			var landStyle = cxt.createLinearGradient(0,800,0,0);
			landStyle.addColorStop (0.0, '#030');
			landStyle.addColorStop (1.0, '#580');
			cxt.fillStyle = landStyle;
			cxt.fill();

			cxt.restore();
		}

所有程式碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>星空練習</title>
</head>
<body>
	<canvas id="canvas" style="display:block;border: 1px solid #aaa;margin: 50px auto">該瀏覽器不支援canvas,請更換瀏覽器後再試</canvas>
	<script type="text/javascript">

		window.onload = function(){
			var canvas = document.getElementById('canvas');
			canvas.width = 1200;
			canvas.height = 800;
			var cxt = canvas.getContext('2d');

			//線性漸變背景
			// var skyStyle = cxt.createLinearGradient(0,0,0,canvas.height);
			// skyStyle.addColorStop(0.0, 'black');
			// skyStyle.addColorStop(1.0, '#035');

			// cxt.fillStyle = skyStyle;
			// cxt.fillRect(0,0,canvas.width,canvas.height);
			
			//徑向漸變背景
			var skyStyle = cxt.createRadialGradient(canvas.width/2,canvas.height,0,canvas.width/2,canvas.height,canvas.height);
			skyStyle.addColorStop(0.0, '#035');
			skyStyle.addColorStop(1.0, 'black');

			cxt.fillStyle = skyStyle;
			cxt.fillRect(0,0,canvas.width,canvas.height);





			for (var i = 0; i < 200; i++) {
				var r = Math.random()*5+5;//大圓半徑為0-20
				var x = Math.random()*canvas.width;
				var y = Math.random()*canvas.height*0.65;
				var a = Math.random()*360;
				drawStar(cxt,r,x,y,a);
			}

			fillMoon(cxt, 2, 900, 200, 100, 30);
            drawLand(cxt);
						
		}

	function drawLand(cxt){
			cxt.save();
			cxt.beginPath();
			cxt.moveTo(0, 600);
			cxt.bezierCurveTo(540, 400, 600, 800, 1200, 600);
			cxt.lineTo(1200,800);
			cxt.lineTo(0,800);
			cxt.closePath();

			var landStyle = cxt.createLinearGradient(0,800,0,0);
			landStyle.addColorStop (0.0, '#030');
			landStyle.addColorStop (1.0, '#580');
			cxt.fillStyle = landStyle;
			cxt.fill();

			cxt.restore();
		}


		function drawStar(cxt,R,x,y,rot){//rot表示旋轉角度

			cxt.save();

			cxt.translate(x,y);
			cxt.rotate(rot/180*Math.PI);
			cxt.scale(R,R);

			starPath(cxt)


			cxt.fillStyle = '#fb3';
			// cxt.strokeStyle = 'fd5';
			// cxt.lineWidth = 3;
			// cxt.lineJoin = 'round';


			cxt.fill();
			//cxt.stroke();

			cxt.restore();

		}

		function starPath(cxt){//繪製一個標準的五角星
			cxt.beginPath();
			for (var i = 0; i < 5; i++) {
				cxt.lineTo(Math.cos( (18+i*72)/180*Math.PI),
					-Math.sin((18+i*72)/180*Math.PI));

				cxt.lineTo(Math.cos( (54+i*72)/180*Math.PI)*0.5,
					-Math.sin((54+i*72)/180*Math.PI)*0.5);

			}
			cxt.closePath();
		}


		function fillMoon(cxt, d, x, y, R, rot, /*optional*/fillColor){
			cxt.save();
			cxt.translate(x,y);
			cxt.rotate(rot*Math.PI/180);
			cxt.scale(R,R);
			pathMoon(cxt, d);
			cxt.fillStyle = fillColor || '#fb5';
			cxt.fill();
			cxt.restore();
		}

		function pathMoon(cxt, d){
			cxt.beginPath();
			cxt.arc(0, 0, 1, 0.5*Math.PI, 1.5*Math.PI, true);
			cxt.moveTo(0, -1);
			cxt.arcTo(d, 0, 0, 1, dis(0, -1, d, 0)/d);
            // cxt.quadraticCurveTo(1.2,0,0,1);利用貝塞爾二次曲線繪製
			cxt.closePath();
		}


		function dis(x1, y1, x2, y2){//直角三角形求取斜邊
			return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
		}
	</script>
</body>
</html>

程式碼中還有線性漸變的背景色被註釋掉了,感興趣的小夥伴可以嘗試一下線性背景色哦 

補充:

對於月亮的繪製,其實不一定要用arcTo繪製,也可以嘗試使用貝塞爾二次曲線來繪製

// cxt.quadraticCurveTo(1.2,0,0,1);利用貝塞爾二次曲線繪製,可以用這個代替arcTo曲線