我想熟悉一下canvas的使用,参考一个例子做了一个能动态展示当前时刻的canvas时钟。
使用canvas绘图第一步肯定是先获取canvas元素对象,并定义上下文。
var clock = document.getElementById("clock");
clock.style.backgroundColor = "black";
var graph = null;
graph = clock.getContext("2d");
然后开始绘制内外圆:
beginPath()方法是路径的开始,stroke()是绘制前面beginPath()之间定义的路径,假如你想画两条两条颜色等设置不一样的路径,那你就需要各自用一个beginPath()去开始路径,否则在同一个beginPayh()后面的两个不同的strokeStyle样式只能表现出后定义的那一个设置。比如说我在下面代码中对画内圆部分前加一行graph.strokeStyle = "white"的话,得不到两个不同颜色的圆,两个圆都会是白色的。
graph.beginPath();
//画外圆
graph.strokeStyle = "yellow";
graph.moveTo(600,300);
graph.arc(300,300,outside_circle_r,0,2*Math.PI,false);
//画内圆
graph.moveTo(590,300);
graph.arc(300,300,inside_circle_r,0,2*Math.PI,false);
graph.stroke();
刻度:
每一条线段的绘制都需要知道两个端点的坐标,刻度端点的坐标就需要使用三角函数了,可以以默认的左上角为原点,也可以使用translate方法把坐标原点移动到时钟的圆心,后者计算坐标更简单。但是我用的是笨方法,以左上角为坐标原点来想办法计算各个刻度的坐标值,小时刻度和分钟刻度长度不一样,但坐标计算方法是一样的,我写了一个函数drawHourAndMinuteLine去封装了计算过程,然后针对小时刻度和分钟刻度用对应传参调用了两次。刻度部分我是在看参考例子前做的,以画布左上角作为坐标原点,实现过程比较复杂,其实没有必要,所以就不解释这部分我写的代码了。这一部分可以参考文章开头的参考例子链接,以时钟中心点作为圆点更简单。
//画小时刻度
var hour_len = 30;//刻度长度
var hourAngleArray = [];
for(let i = 0;i < 4;i++){
hourAngleArray.push(i*Math.PI/6);
}
drawHourAndMinuteLine(hourAngleArray,hour_len);
//画分钟刻度
var minuteAngleArray = [];
var minute_len = 10;//刻度长度
for(let i = 0;i < 15;i++){
minuteAngleArray.push(i*Math.PI/30);
}
drawHourAndMinuteLine(minuteAngleArray,minute_len);
文本:
绘制文本的基本过程都是相同的,不同的只是样式,坐标参数,所以我也写了函数封装。
function drawText(fillStyle,font,textAlign,textBaseline,size,x,y){
graph.fillStyle = fillStyle;
graph.font = font;
graph.textAlign = textAlign;
graph.textBaseline = textBaseline;
graph.fillText(size,x,y);
}
比如说我绘制上下左右的12、6、9、3数字文本的时候就直接调用这个函数:
//画刻度文本值
drawText("yellow","bold 18px Arial","center","middle","12",300,48);
drawText("yellow","bold 18px Arial","center","middle","3",552,300);
drawText("yellow","bold 18px Arial","center","middle","6",300,552);
drawText("yellow","bold 18px Arial","center","middle","9",48,300);
时针、分针和秒针:
为了实现在当前时刻时针指向正确数字的功能实现,必须把时间和时针转过的角度之间建立一个关系,还要确定每根针的长度。这一部分参考了例子,以时钟圆心作为原点实现。
首先获得时间,并在角度和时间之间建立数学关系:
var hourLength = 180;
var minuteLength = 204;
var secondLength = 240;
var date = new Date();
var hour = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
if (hour > 12) {
hour -= 12;
}
var hourAngle = (hour * 30 - 90) * Math.PI / 180;
var minutesAngle = (minutes * 6 - 90) * Math.PI / 180;
var secondsAngle = (seconds * 6 - 90) * Math.PI / 180;
绘制三根针:
//分针
graph.save();
graph.beginPath();
graph.translate(300, 300);
graph.strokeStyle = "yellow";
graph.moveTo(0, 0);
graph.lineTo(minuteLength * Math.cos(minutesAngle), minuteLength * Math.sin(minutesAngle));
graph.stroke();
//秒针
graph.save();//保存新的圆点
graph.beginPath();
graph.strokeStyle = "red";
graph.moveTo(0, 0);
graph.lineTo(secondLength * Math.cos(secondsAngle), secondLength * Math.sin(secondsAngle));
graph.stroke();
graph.stroke();
//时针
graph.beginPath();
graph.strokeStyle = "blue";
graph.moveTo(0, 0);
graph.lineTo(hourLength * Math.cos(hourAngle), hourLength * Math.sin(hourAngle));
graph.stroke();
graph.restore();
graph.restore();
上面代码我用到了两次save()和两次restore()方法,save()方法用于保存前面的各种设置,restore()方法用于退回到上一次save()所保存的设置。因为在我的代码中,只有绘制三根针的时候原点设置才是时钟的中心,所以我的使用主要是为了保证只有在这一部分代码中,原点的设置是时钟中心。
时钟上方的报时文本:
文本绘制还是使用前面提到的一个自定义函数,但需要随着时间更新文本,如果只是简单地每秒绘制一次文本,那不同文本会在同一个区域重叠,所以必须在绘制新的文本时把前一秒绘制的文本去掉,我的办法是每次画一个和背景色相同颜色的矩形框把文本盖住。
/*画出时间文本并定时清除*/
function drawTimeText(){
var date = new Date();
var timeText1 = date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
var timeText2 = date.getFullYear()+"年"+(date.getMonth()+1)+"月"+date.getDate()+"日";
drawText("yellow","bold 30px Arial","center","top",timeText1,295,162);
drawText("yellow","bold 16px Arial","center","top",timeText2,295,200);
setTimeout("drawClearRect()",980);//清除矩形区域的内容
}
function dateText(time){
graph.beginPath();
//表内文本
drawTimeText();
setInterval("drawTimeText()",time);
graph.stroke();
graph.restore();
}
时针变化:
最后我把前面所有绘制部分的代码放到一个函数里,并添加两行清除整个时钟的代码,使用的是clearRect()方法清除区域,否则你会得到一圈60根秒针,然后用setInterval()每秒调用一次这个函数。
if (clock.getContext) {
drawClock();
dateText(1001);
setInterval(drawClock, 1000);
}
上面dateText给的时间1001ms是为了把显示报时文本的时间和drawClock这个绘制整个时钟的函数中清除整个时钟的时刻错开,但这也是一个bug,随着时间它所引起的时间差会越来越大,暂时没有想到怎么解决这个问题。
完整代码:
Canvas
var clock = document.getElementById("clock");
clock.style.backgroundColor = "black";
var graph = null;
var outside_circle_r = 300;/*外圆半径*/
var inside_circle_r = 290;/*内圆半径*/
if (clock.getContext) {
drawClock();
dateText(1001);
setInterval(drawClock, 1000);
}
function drawClock() {
var hourLength = 180;
var minuteLength = 204;
var secondLength = 240;
var date = new Date();
var hour = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
if (hour > 12) {
hour -= 12;
}
var hourAngle = (hour * 30 - 90) * Math.PI / 180;
var minutesAngle = (minutes * 6 - 90) * Math.PI / 180;
var secondsAngle = (seconds * 6 - 90) * Math.PI / 180;
graph = clock.getContext("2d");
/*先清除整个图*/
graph.clearRect(0, 0, clock.width, clock.height);
graph.strokeStyle = "black";
graph.save();
graph.beginPath();
//画外圆
graph.strokeStyle = "white";
graph.moveTo(600,300);
graph.arc(300,300,outside_circle_r,0,2*Math.PI,false);
//画内圆
graph.moveTo(590,300);
graph.arc(300,300,inside_circle_r,0,2*Math.PI,false);
graph.stroke();
//画三个指针的交接处
graph.beginPath();
graph.strokeStyle = "#ff0000";
graph.moveTo(305,300);
for(let i = 0;i <= 10;){//填充圆里面的颜色
graph.arc(300,300,i,0,2*Math.PI,false);
i += 0.5;
}
graph.stroke();
//画小时刻度
var hour_len = 30;//刻度长度
var hourAngleArray = [];
for(let i = 0;i < 4;i++){
hourAngleArray.push(i*Math.PI/6);
}
drawHourAndMinuteLine(hourAngleArray,hour_len);
//画分钟刻度
var minuteAngleArray = [];
var minute_len = 10;//刻度长度
for(let i = 0;i < 15;i++){
minuteAngleArray.push(i*Math.PI/30);
}
drawHourAndMinuteLine(minuteAngleArray,minute_len);
//画刻度文本值
drawText("yellow","bold 18px Arial","center","middle","12",300,48);
drawText("yellow","bold 18px Arial","center","middle","3",552,300);
drawText("yellow","bold 18px Arial","center","middle","6",300,552);
drawText("yellow","bold 18px Arial","center","middle","9",48,300);
//分针
graph.save();
graph.beginPath();
graph.translate(300, 300);
graph.strokeStyle = "yellow";
graph.moveTo(0, 0);
graph.lineTo(minuteLength * Math.cos(minutesAngle), minuteLength * Math.sin(minutesAngle));
graph.stroke();
//秒针
graph.save();//保存新的圆点
graph.beginPath();
graph.strokeStyle = "red";
graph.moveTo(0, 0);
graph.lineTo(secondLength * Math.cos(secondsAngle), secondLength * Math.sin(secondsAngle));
graph.stroke();
graph.stroke();
//时针
graph.beginPath();
graph.strokeStyle = "blue";
graph.moveTo(0, 0);
graph.lineTo(hourLength * Math.cos(hourAngle), hourLength * Math.sin(hourAngle));
graph.stroke();
graph.restore();
graph.restore();
}
/*时间文本*/
function dateText(time){
graph.beginPath();
//表内文本
drawTimeText();
setInterval("drawTimeText()",time);
graph.stroke();
graph.restore();
}
/*画出时间文本并定时清除*/
function drawTimeText(){
var date = new Date();
var timeText1 = date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
var timeText2 = date.getFullYear()+"年"+(date.getMonth()+1)+"月"+date.getDate()+"日";
drawText("yellow","bold 30px Arial","center","top",timeText1,295,162);
drawText("yellow","bold 16px Arial","center","top",timeText2,295,200);
setTimeout("drawClearRect()",980);//清除矩形区域的内容
}
function drawText(fillStyle,font,textAlign,textBaseline,size,x,y){
graph.fillStyle = fillStyle;
graph.font = font;
graph.textAlign = textAlign;
graph.textBaseline = textBaseline;
graph.fillText(size,x,y);
}
/*画一条线段的函数*/
function drawStraightLine(color,X,Y,toX,toY){
graph.beginPath();
graph.strokeStyle = color;
graph.moveTo(X,Y);
graph.lineTo(toX,toY);
graph.stroke();
}
/*画出时间刻度*/
function drawHourAndMinuteLine(timeType,len){
for(let i = 0;i < timeType.length;i++){
let x = outside_circle_r+inside_circle_r*Math.cos(timeType[i]);
let y = outside_circle_r-inside_circle_r*Math.sin(timeType[i]);
let toX = x-len*Math.cos(timeType[i]);
let toY = y+len*Math.sin(timeType[i]);
let toXLeftTop = outside_circle_r-inside_circle_r*Math.cos(timeType[i])+len*Math.cos(timeType[i]);
let toYRightBottom = outside_circle_r+inside_circle_r*Math.sin(timeType[i]);
drawStraightLine("white",x,y,toX,toY);//右上刻度
drawStraightLine("white",outside_circle_r-inside_circle_r*Math.cos(timeType[i]),y,toXLeftTop,toY);//左上刻度
drawStraightLine("white",x,toYRightBottom,toX,toYRightBottom-len*Math.sin(timeType[i]));//右下刻度
drawStraightLine("white",outside_circle_r-inside_circle_r*Math.cos(timeType[i]),toYRightBottom,toXLeftTop,toYRightBottom-len*Math.sin(timeType[i]));//左下刻度
}
}
/*清除文本时间显示区*/
function drawClearRect(){
graph.clearRect(235,162,120,55);
}