用 Canvas 绘制一个动态时钟

  • 2016-05-30
  • 6
  • 0

开始绘制之前首先要了解 Canvas 有哪些属性以及哪些用法。

Canvas 的常用方法跟基础属性:

1>.Canvas.getContext(contextID);
返回一个表示用来绘制的环境类型的环境。其本意是要为不同的绘制类型(2 维、3 维)提供不同的环境。当前,唯一支持的是 “2d”,它返回一个 CanvasRenderingContext2D 对象,该对象实现了一个画布所使用的大多数方法。

2>.context.lineCap=”round”;
lineCap 属性设置或返回线条末端线帽的样式。(类似border-radius:**)
注释:”round” 和 “square” 会使线条略微变长。
3>.context.beginPath();
beginPath() 方法开始一条路径,或重置当前的路径。
context.stroke();方法是结束一条路径;

4>.context.save();
保存当前环境的状态;

5>.context.strokeStyle;
strokeStyle 属性设置或返回用于笔触的颜色、渐变或模式。

6>.context.fillStyle;
fillStyle 属性设置或返回用于填充绘画的颜色、渐变或模式。

7>.context.arc; 画圆;
context.arc(圆心的X坐标、圆心Y坐标、圆半径长度、圆的起始角、圆的结束角、顺时针或者逆时针绘图);

8>.context.fill();
fill() 方法填充当前的图像(路径)。默认颜色是黑色。

9>.context.shadowBlur;
shadowBlur 属性设置或返回阴影的模糊级数。

10>.context.restore();
将绘图状态置为保存值。

11>.context.translate;
translate() 方法重新映射画布上的 (0,0) 位置。

12>.context.rotate;
rotate() 方法旋转当前的绘图。

13>.context.fillText(”,”,”,”);
fillText() 方法在画布上绘制填色的文本。文本的默认颜色是黑色。
text规定在画布上输出的文本。x开始绘制文本的 x 坐标位置(相对于画布)。y开始绘制文本的 y 坐标位置(相对于画布)。 maxWidth可选。允许的最大文本宽度,以像素计。
14>.context.clearRect(x,y,width,height);
clearRect() 方法清空给定矩形内的指定像素。
参数描述x要清除的矩形左上角的 x 坐标 y 要清除的矩形左上角的 y 坐标 width 要清除的矩形的宽度,以像素计 height 要清除的矩形的高度,以像素计。

温习过 Canvas 之后便可以整理思路,开始制定开发流程啦:

Canvas 绘制时钟流程:

1>创建绘图空间,设置绘图区的初始参数(字体大小、字体颜色、笔触形状等);
2>绘制坐标轴,X轴和Y轴 drawXY()
3>绘制表盘 drawCircle()
4>绘制刻度盘(逢5倍数刻度长度加倍)drawKedu()
5>绘制时钟圆心 drawCenter()
6>绘制数字刻度盘(逢3倍数数字字号加大加粗)drawNumbers()
7>绘制时针、分针、秒针 drawHandler()drawHandlerItem(a,raduis,lineWidth,color)calc(h,m,s,ms)
8>创建绘制时钟函数 drawClock()
9>每秒调用一次绘制函数实现动态效果 main()

有了清晰的开发思路便可以一步步的吧代码写出来了:

完整 JS 代码:

var canvas = document.getElementById("canvas"),
	context = canvas.getContext("2d"),
	c_raduis=150,
	c_raduis_numbers =c_raduis+ 25,
	c_raduis_secound =c_raduis,
	c_raduis_m =c_raduis_secound-25,
	c_raduis_h =c_raduis_m-25;
	
	context.font="15pt Arial";
	context.textAlign="center";
	context.textBaseline="middle";
	context.shadowColor="rgba(100,100,150,.8)";
	context.lineCap="square";
	context.shadowOffsetX=5;
	context.shadowOffsetY=5;
	context.shadowBlur=10;
	
	function drawXY(){
		context.beginPath();
		context.lineWidth="2";
		context.strokeStyle="red";
		context.moveTo(0,canvas.height/2);
		context.lineTo(canvas.width,canvas.height/2);
		context.stroke();
		
		context.beginPath();
		context.lineWidth="2";
		context.strokeStyle="gray";
		context.moveTo(canvas.width/2,0);
		context.lineTo(canvas.width/2,canvas.height);
		context.stroke();
	}

	function drawCircle(){
		context.save();
		context.shadowColor="rgba(0,0,0,.7)";
		context.strokeStyle="rgba(100,240,130,.5)";
		context.fillStyle="rgba(171,208,174,.3)";
		context.beginPath();
		context.arc(canvas.width/2,canvas.height/2,c_raduis_numbers+30,0,Math.PI*2,false);
		context.arc(canvas.width/2,canvas.height/2,c_raduis,0,Math.PI*2,true);
		context.stroke();
		context.fill();
		context.restore();
	}

	function drawKedu(){
		var x,y,w1=5,w2=10,x1,y1,a;
		context.save();
		context.strokeStyle="rgba(135,199,140,.7)";
		context.beginPath();
		for(var angle=0,index=0;angle<=Math.PI*2;angle+=Math.PI/30,index++){
			if(index%5==0){
				a=w2;
			}else{
				a=w1;
			}
			x=canvas.width/2+Math.cos(angle)*c_raduis;
			y=canvas.height/2+Math.sin(angle)*c_raduis;
			x1=canvas.width/2+Math.cos(angle)*(c_raduis-a);
			y1=canvas.height/2+Math.sin(angle)*(c_raduis-a);
			context.moveTo(x,y);
			context.lineTo(x1,y1);
		}
		context.stroke();
		context.strokeStyle="rgba(6,158,18,.3)";
		context.arc(canvas.width/2,canvas.height/2,c_raduis-w2,0,Math.PI*2,false);
		context.stroke();
		context.restore();
	}

	function drawCenter(){
		context.beginPath();
		context.arc(canvas.width/2,canvas.height/2,6,0,Math.PI*2,true);
		context.fill();
	}

	function drawNumbers(){
		var x,y,nums=[1,2,3,4,5,6,7,8,9,10,11,12],numberWidth;
		nums.forEach(function(num){
			context.save();
			context.fillStyle="rgba(250,100,200,1)";
			context.beginPath();
			var angle = Math.PI/6*num-Math.PI/2;
			x=canvas.width/2+Math.cos(angle)*c_raduis_numbers;
			y=canvas.height/2+Math.sin(angle)*c_raduis_numbers;
			context.translate(x,y);
			context.rotate(Math.PI/2+angle)
			if(num==3) context.fillText("3",0,0);
			else if(num==6) context.fillText("6",0,0);
			else if(num==9) context.fillText("9",0,0);
			else if(num==12) context.fillText("12",0,0);
			else {
				context.font="10pt Arial";
				context.fillStyle="rgba(100,140,230,1)";
				context.fillText(num,0,0);
			}
			context.restore();
		});
		
	}

	function drawHandler(){
		var date = new Date();
		drawHandlerItem(calc(null,null,date.getSeconds(),date.getMilliseconds())
			,c_raduis_secound,2,"rgba(255,0,0,.5)");
		drawHandlerItem(calc(null,date.getMinutes(),date.getSeconds(),date.getMilliseconds())
			,c_raduis_m,4,"rgba(0,0,0,.9)");
		drawHandlerItem(calc(date.getHours(),date.getMinutes(),date.getSeconds(),date.getMilliseconds())
			,c_raduis_h,6,"rgba(0,0,0,.9)");
	}
	
	function drawHandlerItem(a,raduis,lineWidth,color){
		var x,y,o={x:canvas.width/2,y:canvas.height/2};
		x=o.x+Math.sin(Math.PI-(2*Math.PI)/a)*raduis;
		y=o.y+Math.cos(Math.PI-(2*Math.PI)/a)*raduis;
		context.save();
		context.lineWidth=lineWidth;
		context.strokeStyle=color;
		context.beginPath();
		context.moveTo(canvas.width/2,canvas.height/2);
		context.lineTo(x,y);
		context.stroke();
		context.restore();
	}
	
	function calc(h,m,s,ms){
		var count
		,current
		,h1=h||0
		,m1=m||0
		,s1=s||0
		,ms1=ms||0
		;
		h1=h1%12;
		current = h1*60*60*1000+m1*60*1000+s1*1000+ms1;

		if(h!=null)count=12*60*60*1000;
		else if(m!=null)count=60*60*1000;
		else if(s!=null)count=60*1000;
		else if(ms!=null)count=1000;
		return count/current;
	}

	function drawClock(){
		context.clearRect(0,0,canvas.width,canvas.height);
		//drawXY();
		drawCircle();
		drawKedu();
		drawCenter();
		drawNumbers();
		drawHandler();
	}

	function main(a){
		drawClock();
	    setTimeout(main,40);
	}
	main();

把开发思路详细理一理:

绘制时钟步骤详解

1>在页面中圈出一个500px*500px的区域canvas,并为其创建2维绘图环境 canvas.getContext("2d");为刻度盘半径(c_raduis_numbers)、时针半径(c_raduis_h)等变量设置初始值;设置绘图区间的笔触样式 context.lineCap、阴影颜色 context.shadowColor 等变量;

var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
c_raduis=150,
c_raduis_numbers =c_raduis+ 25,
c_raduis_secound =c_raduis,
c_raduis_m =c_raduis_secound-25,
c_raduis_h =c_raduis_m-25;

context.font="15pt Arial";
context.textAlign="center";
context.textBaseline="middle";
context.shadowColor="rgba(100,100,150,.8)";
context.lineCap="square";
context.shadowOffsetX=5;
context.shadowOffsetY=5;
context.shadowBlur=10;

2>通过画线方法 context.moveTo()context.lineTo()绘制坐标轴;

function drawXY(){
	context.beginPath();
	context.lineWidth="2";
	context.strokeStyle="red";
	context.moveTo(0,canvas.height/2);
	context.lineTo(canvas.width,canvas.height/2);
	context.stroke();
	
	context.beginPath();
	context.lineWidth="2";
	context.strokeStyle="gray";
	context.moveTo(canvas.width/2,0);
	context.lineTo(canvas.width/2,canvas.height);
	context.stroke();
}

3>通过方法 context.arc() 绘制两个圆,context.fill() 方法给两圆组成的密封区域填色作为表盘;

function drawCircle(){
	context.save();
	context.shadowColor="rgba(0,0,0,.7)";
	context.strokeStyle="rgba(100,240,130,.5)";
	context.fillStyle="rgba(171,208,174,.3)";
	context.beginPath();
	context.arc(canvas.width/2,canvas.height/2,c_raduis_numbers+30,0,Math.PI*2,false);
	context.arc(canvas.width/2,canvas.height/2,c_raduis,0,Math.PI*2,true);
	context.stroke();
	context.fill();
	context.restore();
}

4>以时钟3点整(即数学中第一象限的X轴作为0度角),以角度作为循环变量定义一个循环方法来画表盘刻度;以 Math.PI/30(即表盘上每分钟所占的角度)作为每次循环角度的递增量,通过三角函数 cos() sin() 计算出刻度起点和终点的坐标,再通过 moveTo() 方法依此画出每个时刻的刻度;

function drawKedu(){
	var x,y,w1=5,w2=10,x1,y1,a;
	context.save();
	context.strokeStyle="rgba(135,199,140,.7)";
	context.beginPath();
	for(var angle=0,index=0;angle<=Math.PI*2;angle+=Math.PI/30,index++){
		if(index%5==0){
			a=w2;
		}else{
			a=w1;
		}
		x=canvas.width/2+Math.cos(angle)*c_raduis;
		y=canvas.height/2+Math.sin(angle)*c_raduis;
		x1=canvas.width/2+Math.cos(angle)*(c_raduis-a);
		y1=canvas.height/2+Math.sin(angle)*(c_raduis-a);
		context.moveTo(x,y);
		context.lineTo(x1,y1);
	}
	context.stroke();
	context.strokeStyle="rgba(6,158,18,.3)";
	context.arc(canvas.width/2,canvas.height/2,c_raduis-w2,0,Math.PI*2,false);
	context.stroke();
	context.restore();
}

5>调用 context.arc() 方法绘制半径为6px的圆,并用默认的黑色填充,作为时钟的圆心;

function drawCenter(){
	context.beginPath();
	context.arc(canvas.width/2,canvas.height/2,6,0,Math.PI*2,true);
	context.fill();
}

6>数字表盘显示的是时针的刻度,可以根据(1、2、3、4…)等时刻数来计算出当前时针走过的角度(跟步骤4类似),通过三角函数计算出当前时刻的坐标值;再用映射函数 translate() 将数字根据计算出来的坐标值映射到相应的位置,根据当前刻度的角度通过旋转函数rotate()将数字旋转;

function drawNumbers(){
	var x,y,nums=[1,2,3,4,5,6,7,8,9,10,11,12],numberWidth;
	nums.forEach(function(num){
		context.save();
		context.fillStyle="rgba(250,100,200,1)";
		context.beginPath();
		var angle = Math.PI/6*num-Math.PI/2;
		x=canvas.width/2+Math.cos(angle)*c_raduis_numbers;
		y=canvas.height/2+Math.sin(angle)*c_raduis_numbers;
		context.translate(x,y);
		context.rotate(Math.PI/2+angle)
		if(num==3) context.fillText("3",0,0);
		else if(num==6) context.fillText("6",0,0);
		else if(num==9) context.fillText("9",0,0);
		else if(num==12) context.fillText("12",0,0);
		else {
			context.font="10pt Arial";
			context.fillStyle="rgba(100,140,230,1)";
			context.fillText(num,0,0);
		}
		context.restore();
	});
}

7>定义一个时间对象 date = new Date(),获取当前的时刻 date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()
绘制指针(以时针为例,分针、秒针类似),时针走一圈的毫秒数总数为 count=12*60*60*1000,当前时针所走的毫秒数为 current=h1*60*60*1000+m1*60*1000+s1*1000+ms1,val=current/count 即为时针当前所走角度与整个刻度盘角度的百分比,val*2*Math.PI 就是时针当前所走过的角度啦;接下来又是利用时针的长度(初始变量有设置过,c_raduis_h =c_raduis_m-25)和三角函数计算出时针的针尖的坐标值,最后以圆心为起点,针尖为终点就可以画出当前时刻的时针了;绘制分针和秒针的原理、步骤和绘制时针的一致;(这是最重要也是最难理解的一步了,可以试着慢慢调试着去理解~~)

function drawHandler(){
	var date = new Date();
	drawHandlerItem(calc(null,null,date.getSeconds(),date.getMilliseconds())
		,c_raduis_secound,2,"rgba(255,0,0,.5)");
	drawHandlerItem(calc(null,date.getMinutes(),date.getSeconds(),date.getMilliseconds())
		,c_raduis_m,4,"rgba(0,0,0,.9)");
	drawHandlerItem(calc(date.getHours(),date.getMinutes(),date.getSeconds(),date.getMilliseconds())
		,c_raduis_h,6,"rgba(0,0,0,.9)");
}

function drawHandlerItem(a,raduis,lineWidth,color){
	var x,y,o={x:canvas.width/2,y:canvas.height/2};
	x=o.x+Math.sin(Math.PI-(2*Math.PI)/a)*raduis;
	y=o.y+Math.cos(Math.PI-(2*Math.PI)/a)*raduis;
	context.save();
	context.lineWidth=lineWidth;
	context.strokeStyle=color;
	context.beginPath();
	context.moveTo(canvas.width/2,canvas.height/2);
	context.lineTo(x,y);
	context.stroke();
	context.restore();
}

function calc(h,m,s,ms){
	var count
	,current
	,h1=h||0
	,m1=m||0
	,s1=s||0
	,ms1=ms||0
	;
	h1=h1%12;
	current = h1*60*60*1000+m1*60*1000+s1*1000+ms1;

	if(h!=null)count=12*60*60*1000;
	else if(m!=null)count=60*60*1000;
	else if(s!=null)count=60*1000;
	else if(ms!=null)count=1000;
	return count/current;
}

8>在 drawClock() 函数里依次调用drawCircle(); drawKedu(); drawCenter(); drawNumbers(); drawHandler();方法便可画出当前时刻的一个静止的时钟;

function drawClock(){
	context.clearRect(0,0,canvas.width,canvas.height);
	drawXY();//画坐标轴
	drawCircle();//画表盘
	drawKedu();//画表盘刻度
	drawCenter();//画圆心
	drawNumbers();//画表盘上的数字刻度
	drawHandler();//画时针/分针/秒针(某一时刻状态下的三根指针)
}

9>在 main() 函数里利用定时执行函数 setTimeout(main,20) ;实时刷新指针的位置便可模拟动态的时钟了,将 20ms 改为 1000ms 可让秒针 1s 跳动一下。

function main(a){
	drawClock();
	setTimeout(main,20);
}
main();

评论

还没有任何评论,你来说两句吧