用canvas製作一個時鐘
我想熟悉一下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,隨著時間它所引起的時間差會越來越大,暫時沒有想到怎麼解決這個問題。
完整程式碼:
<!DOCTYPE html>
<html>
<head lang="zh-CN">
<meta charset="UTF-8">
<title>Clock</title>
</head>
<body>
<canvas id="clock" width="600px" height="600px">
Canvas
</canvas>
<script type="text/javascript">
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);
}
</script>
</body>
</html>