闲话少叙,今天准备把HTML5 Canvas相关学习笔记记录下来。
开题第一篇,这里讲讲如何用JS在Canvas上绘制一个钟表。
如今大部分浏览器都支持Canvas元素,如果不确定,可以用语句 document.createElement("canvas").getContext("2d"); 检测一下,不支持的话会抛出异常。
HTML代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="canvasClock.js" defer="defer"></script>
</head>
<body>
<canvas id="canvasClock" width="600" height="300">Canvas not supported!</canvas>
</body>
</html>
HTML代码不做介绍。Canvas元素尺寸我们设置为600*300。(关于Canvas的尺寸,分为"Canvas元素尺寸"和"绘制表面的尺寸",在Canvas标签中指明尺寸,是同时对这两个尺寸都生效的。若在CSS中指明宽高,其对绘制表面尺寸是无效的。)
绘制钟表过程中,首先,我们定义一个Clock对象,包含一些属性和方法,使用setTimeout或setInterval调用绘制钟表函数。流程比较简单。
JS代码在这里:
/**
* js for canvasClock
*/
var canvasClock = document.getElementById("canvasClock");
var canvasContext = canvasClock.getContext("2d");
canvasClock.style.background="#98FB98";
function Clock(splitNums,context){
this.splitNums = splitNums;
this.context = context;
this.marginTopBottom = 20;
this.centerPosX = this.context.canvas.width/2;
this.centerPosY = this.context.canvas.height/2;
this.surfaceRadius = Math.floor((this.context.canvas.height-2*this.marginTopBottom)/2);
this.innerCircieRadius = Math.floor(this.surfaceRadius/15);
this.handCircleRadius = Math.floor(this.surfaceRadius/30);
this.noValue;
}
Clock.prototype={
constructor:Clock,
drawSurface:function(){
this.context.beginPath();
this.context.strokeStyle = "black";
this.context.arc(this.centerPosX,this.centerPosY,
this.surfaceRadius,0,Math.PI*2,true);
this.context.stroke();
},
drawInnerCircle:function(){
this.context.beginPath();
this.context.fillStyle = "red";
this.context.arc(this.centerPosX,this.centerPosY,
this.innerCircieRadius,0,Math.PI*2,true);
this.context.fill();
},
drawIndexLine:function(){
this.context.beginPath();
this.context.strokeStyle = "black";
var totalIndex = this.splitNums*5;
var angleInterval = Math.PI*2/totalIndex;
var shotIndexL = this.surfaceRadius/16;
var longIndexL = this.surfaceRadius/9;
for(var i=0;i<totalIndex;i++){
if(i%5==0){
this.context.moveTo(
this.centerPosX+this.surfaceRadius*Math.cos(angleInterval*i),
this.centerPosY-this.surfaceRadius*Math.sin(angleInterval*i));
this.context.lineTo(
this.centerPosX+(this.surfaceRadius-longIndexL)*Math.cos(angleInterval*i),
this.centerPosY-(this.surfaceRadius-longIndexL)*Math.sin(angleInterval*i)
);
}else{
this.context.moveTo(
this.centerPosX+this.surfaceRadius*Math.cos(angleInterval*i),
this.centerPosY-this.surfaceRadius*Math.sin(angleInterval*i));
this.context.lineTo(
this.centerPosX+(this.surfaceRadius-shotIndexL)*Math.cos(angleInterval*i),
this.centerPosY-(this.surfaceRadius-shotIndexL)*Math.sin(angleInterval*i)
)
}
}
this.context.stroke();
},
drawHands:function(){
var date = new Date();
var hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
this.drawHand(hour,min,sec,0);
this.drawHand(0,min,sec,1);
this.drawHand(0,0,sec,2);
},
drawHand:function(hour,minute,second,timeMark){
//time: hour or minute or second
//timeMark: 0-hour, 1-minute, 2-second
var handLength = 10,
longIndexL = this.surfaceRadius/9;
var totalIndex = this.splitNums*5;
var angleInterval = Math.PI*2/totalIndex;
var endPosX=0,endPosY=0;
this.context.beginPath();
this.context.strokeStyle="#8B1A1A";
this.context.fillStyle="#8B1A1A";
this.context.moveTo(this.centerPosX,this.centerPosY);
switch(timeMark){
case 0: handLength=this.surfaceRadius-longIndexL*3-3;
endPosX = this.centerPosX+handLength*Math.sin(
hour*(angleInterval*5)+
minute*(Math.PI*2/this.splitNums)/60+
second*(Math.PI*2/this.splitNums)/3600);
endPosY = this.centerPosY-handLength*Math.cos(
hour*(angleInterval*5)+
minute*(Math.PI*2/this.splitNums)/60+
second*(Math.PI*2/this.splitNums)/3600);
break;
case 1: handLength=this.surfaceRadius-longIndexL*2-3;
endPosX=this.centerPosX+handLength*Math.sin(
minute*angleInterval+
second*angleInterval/60);
endPosY=this.centerPosY-handLength*Math.cos(
minute*angleInterval+
second*angleInterval/60);
break;
case 2: handLength=this.surfaceRadius-longIndexL-3;
endPosX=this.centerPosX+handLength*Math.sin(
second*angleInterval);
endPosY=this.centerPosY-handLength*Math.cos(
second*angleInterval);
break;
default: break;
}
this.context.lineTo(endPosX,endPosY);
this.context.stroke();
this.context.beginPath();
this.context.arc(endPosX,endPosY,
this.handCircleRadius,0,Math.PI*2,true);
this.context.fill();
},
drawNumbers:function() {
var totalIndex = this.splitNums*5;
var angleInterval = Math.PI*2/totalIndex;
var longIndexL = this.surfaceRadius/5;
var numWidth = 0, numHeight=0;
this.context.fillStyle = "black";
for(var i=0;i<this.splitNums;i++){
numWidth = this.context.measureText(String(i)).width;
this.context.fillText(String(i),
this.centerPosX+(this.surfaceRadius-longIndexL)*Math.sin(angleInterval*5*i)
-numWidth/2,
this.centerPosY-(this.surfaceRadius-longIndexL)*Math.cos(angleInterval*5*i)
+15/3);
}
}
}
var clock = new Clock(12,canvasContext);
canvasContext.font = "15px Arial";
function drawClock() {
canvasContext.clearRect(0,0,canvasContext.canvas.width,canvasContext.canvas.height);
clock.drawSurface();
clock.drawIndexLine();
clock.drawNumbers();
clock.drawHands();
clock.drawInnerCircle();
loop = setTimeout(drawClock, 1000);
}
loop = setTimeout(drawClock, 0);
个人倾向使用setTimeout方法,保证执行完一个canvas绘制流程再进行下一个。
另外使用fillText()方法绘制文字时,注意是把文字的左下角放在绘制点,所以在绘制钟表数字时,要把数字向左下移动(文字宽度/2,文字高度/2),保证数字中心在绘制点上,看起来比较美观。
Clock对象中,splitNums意为表盘显示12小时或24个小时,显示24个小时时,由于秒数、分数都从Date()获取,范围都是0-59,也就只能走表盘的一半,但计时是对的。想改善的同学可以自行优化(使秒数、分数范围在0-119即可)。
效果图: