到第三讲结束时canvas时钟已经有了三根指针,它们可能想有个家,我们应该让它们安家落户了——给它们绘制钟壳和钟盘,同时给指针做一个指针扣,以免将来时钟运行时指针在走动的时候指针散架掉落。
这就是绘制圆和圆环。在canvas画布上画圆有两种方法,一种是 arc() 方法,另一种是 arcTo() 方法,它们实际上都不是画圆,而是画弧线,用它们画圆无非就是画一个头尾彼此衔接的弧线。arcTo() 略显复杂,我们选择 arc() 方法来完成时钟的外壳(含时钟钟盘,当代工业一体化的构造)、指针扣以及将来会加上的圆点刻度等绘制工作。arc() 画弧形需要 6 个参数,多是多了点但不要方,下面的代码有详细说明:
/* arc() 语法:
ctx.arc(x, y, r, startAngle, endAngle, anticlockwise);
参数详解:
x,y - 圆心XY坐标值
r - 圆的半径
startAngle - 绘制弧线起始点弧度
endAngle - 绘制弧线结束点弧度
anticlockwise - 绘制弧线的方向,true = 逆时针绘制, false = 顺时针绘制(默认)
*/
/* 例:以 150,150 为圆心,半径 140 画一个圆
arc() 方法描述的是绘制路径,并没有填充和描边功能,所以描述之后:
① 用 fill() 方法绘制实心圆
② 用 stroke() 方法绘制圆环
凡绘制与路径相关的图形,均需要开启路径 beginPath() 指令,表示所绘制的图形路径与之前可能存在的任何路径无关
*/
ctx.fillStyle = 'tan'; //填充色
ctx.strokeStyle = 'darkgreen'; //描边色
ctx.beginPath(); //开启新路径
ctx.arc(150, 150, 140, 0, Math.PI * 2, false); //描述圆的路径,其中Math.PI是弧度,后面解释
ctx.fill(); //给上述路径填充颜色
ctx.stroke(); //给上述路径描边
靓丽的样纸如下所示:
上例代码中,从 0 弧度开始画圆,就是从三点钟方向开始,到圆的终点不论是顺时针还是逆时针绘制都是绕一圈后回到原点,360度。360度要换算成弧度,弧度和角度的关系以及360度转弧度的推演如下:
弧度 = Math.PI / 180 * 角度
转一圈的弧度 = Math.PI / 180 * 360 → 转一圈的弧度 = Math.PI * 2
Math.PI 就是数学中的派,π,圆周率。下来我们将画圆的 arc() 方法也封装一下,方面我们将来画多个圆形图案:
/* 函数 :画圆
x,y,r - 圆心XY坐标坐标和半径r
lw - lineWidth,描边线宽
color1,color2 - 填充色和描边色
*/
let draw_circle = (x,y,r,lw,color1,color2) => {
ctx.save();
ctx.fillStyle = color1;
ctx.strokeStyle = color2;
ctx.lineWidth = lw;
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.restore();
};
//函数调用举例:
draw_circle(150,150,140,10,'pink','red');
画圆无需转换画布的坐标系,但仍然在函数中使用了 save() 和 reatore() 方法,因为ctx的填充色、描边色和线宽等也是画布的设置状态,保存和复原画布状态能让我们的整体绘制工作中的每一个环节换不干扰。beginPath() 方法除了 fillRect() 和 strokeRect() 这两个画矩形的不需要,画圆等都少不了它,切记。
最后,我们将上一讲的指针搬过来,看看效果如何:
嗯,现在指针可以欢歌了:我想要有个家,一个哪怕是空荡荡的地方……
以上效果的实现代码:
<canvas id="canv" width="300" height="300"></canvas></div>
<script>
let ctx = canv.getContext('2d'); //获取画笔
// 函数 :画指针
let draw_rect = (x, y, w, h, rad, color) => {
ctx.save();
ctx.fillStyle = color;
ctx.translate(150,150);
ctx.rotate(rad);
ctx.fillRect(x,y,w,h);
ctx.restore();
};
// 函数 :画圆
let draw_circle = (x,y,r,lw,color1,color2) => {
ctx.save();
ctx.fillStyle = color1;
ctx.strokeStyle = color2;
ctx.lineWidth = lw;
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.restore();
};
///下面画各时钟元素,注意顺序
draw_circle(150,150,140,10,'tan','darkgreen'); //钟盘
draw_rect(0, -5, 100, 10, 0 * Math.PI/180, 'snow'); //时针
draw_rect(0, -3, 120, 6, 270 * Math.PI/180, 'white'); //分针
draw_rect(0, -2, 130, 4, 240 * Math.PI/180, 'red'); //秒钟
draw_circle(150,150,6,6,'white','red'); //指针扣
</script>
留意一下时针、分针和秒针的旋转参数所用的计算式子,这里先略过,在未来章节合适的地方我会对此进行解释。