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曲線