用變量的方式繪制一個五角星,首先求五角星十個頂點的坐標。
可以把每個五角星看成外頂點用一個大圓繪制,內頂點用小圓繪制。在坐標系(0deg)下,根據每個頂點的角度和圓的半徑求得x,y。
而每個大頂點相差72deg(180/5),每個小頂點也差72deg.所以下一個頂點的度數就是當前點加上72deg.(逆時針)
代碼實現畫一個五角星
function drawStar(context, R, r, x, y,rot) { // R:大圓半徑,r:小圓半徑,x: x方向圓心位置 y: y方向圓心位置,rot:旋轉角度 context.beginPath(); for (var i = 0; i < 5; i++) {
//因為角度是逆時針計算的,而旋轉是順時針旋轉,所以是度數是負值。 context.lineTo(x + Math.cos((18 + 72 * i - rot) / 180 * Math.PI) * R, y - Math.sin((18 + 72 * i - rot) / 180* Math.PI)* R); context.lineTo(x + Math.cos((54 + 72 * i - rot) / 180 * Math.PI) * r, y - Math.sin((54 + 72 * i - rot) / 180 * Math.PI) * r); } context.closePath(); context.stroke(); }
畫多個五角星
for (var i = 0; i < 200; i++) { var r = Math.random() * 10 + 10; //大小隨機 var x = Math.random() * canvas.width; //位置隨機 var y = Math.random() * canvas.height; var rot = Math.random() * 360; //星星旋轉角度隨機
drawStar(context, r, r / 2, x, y, rot);
} function drawStar(context, R, r, x, y, rot) { context.beginPath(); for (var i = 0; i < 5; i++) { context.lineTo(x + Math.cos((18 + 72 * i - rot) / 180 * Math.PI) * R, y - Math.sin((18 + 72 * i - rot) / 180 * Math.PI) * R); context.lineTo(x + Math.cos((54 + 72 * i - rot) / 180 * Math.PI) * r, y - Math.sin((54 + 72 * i - rot) / 180 * Math.PI) * r); } context.closePath(); context.fillStyle = '#fb3'; context.strokeStyle = '#fd5'; context.lineWidth = 3; context.lineJoin = 'round'; context.fill(); context.stroke(); }
然而上面代碼並不符合軟件工程的設計原則,一個函數包含了很多功能(繪制星星和位移,旋轉角度都放在一個方法里)。當需求發生變化時,例如將五角星變成圓或多邊形,就需要重新修改整個方法。在圖形學中任何的圖形學都使用先繪制基本輪廓再根據需求進行圖形變換。也就是說一個方法繪制原始圖形的路徑,一個方法對圖形進行變換(位移,角度,大小...) , 用戶也可以直接傳一個圖形的路徑或方法,根據路徑進行繪制。
圖形變換
- context.translate(x, y); 對canvas坐標系進行整體位移
- context.rotate(angle); 給canvas畫布添加旋轉矩陣,順時針方向默認旋轉中心點是Canvas的左上角(0, 0)坐標點,如果希望改變旋轉中心點,例如以Canvas畫布的中心旋轉,需要先使用translate()位移旋轉中心點。
注意:以上兩個方法平移,旋轉的是坐標系,而非元素。因此,實際開發的時候,平移或旋轉完畢,需要將坐標系再還原。使用save(),restore().
-
context.scale(x, y);用來縮放Canvas畫布的坐標系,只是影響坐標系,之后的繪制會受此方法影響,但之前已經繪制好的效果不會有任何變化。因此下面代碼雖然起始點一樣,但是由於坐標系被放大,使得放大后的圖形起始點改變。如果設置lineWidth,也會放大。使用時要注意。
context.strokeRect(50, 50, 100, 100);
context.scale(2, 2);
context.strokeRect(50, 50, 100, 100);
for (var i = 0; i < 200; i++) {
var x = Math.random() * canvas.width;
var y = Math.random() * canvas.height;
var rot = Math.random() * 360;
var r = Math.random() * 10 + 10;
transDraw(context, x, y, r,rot);
}
// 用戶可以直接傳一個圖形的路徑或方法,根據路徑進行繪制。
function transDraw(context, x, y, r, rot){
context.save(); //將當前狀態壓入棧中。
context.translate(x, y);
context.scale(r, r);
context.rotate(Math.PI / 180 * rot);
context.fillStyle = '#fb3';
drawPath(context);
context.fill();
context.restore(); //將變換后的坐標系恢復到之前狀態
}
//繪制一個圖形路徑,無旋轉角度,偏移,大小設置為1的五角星
function drawPath(context) {
context.beginPath();
for (var i = 0; i < 5; i++) {
context.lineTo(Math.cos((18 + 72 * i) / 180 * Math.PI) ,
-Math.sin((18 + 72 * i) / 180 * Math.PI) ) ;
context.lineTo(Math.cos((54 + 72 * i) / 180 * Math.PI) * 0.5,
-Math.sin((54 + 72 * i) / 180 * Math.PI) * 0.5);
}
context.closePath();
}
繪制月亮
最外面的弧用半圓進行繪制,里面的弧使用畫弧的arcTo()方法,傳入的參數為:控制點,終止點,半徑。
起始點,控制點,終止點這些都是根據不同的需求位置可變,所以我們需要根據可以可變的點求出同樣可變的半徑。以AOC為角,因為tan = 對邊 / 鄰邊,所以根據下面的公式可以求出半徑。
AC和AH的長度很好求,而AC的長度只需要使用兩點間距離公式
具體代碼,還是使用先繪制原始路徑,在進行變換。
變換函數drawMoon傳入的參數d代表arcTo的控制點的橫坐標
function drawMoon(context, d, x, y, R, rot, fillColor) {
context.save();
context.translate(x, y);
context.scale(R, R);
context.rotate(Math.PI / 180 * rot);
MoonPath(context, d);
context.fillStyle=fillColor;
context.fill();
context.restore();
}
function MoonPath(context, d) {
context.beginPath();
context.arc(0, 0, 1, Math.PI * 0.5, Math.PI * 1.5, true); //繪制外面的弧
context.arcTo(d, 0, 0, 1, distance(0, -1, d, 0) / d); //繪制里面的弧
}
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
}
最后就可以結合上面的星星月亮來繪制下面的一幅圖