Canvas 畫時鐘
前言
不管學習什麼,不動手去做,永遠不能熟練掌握。學習了 canvas API,會覺得只要按照直線、圓等畫法去畫,canvas 太簡單了。可是,當你真正去畫的時候,會遇到許多的問題。
下面介紹的是 canvas 時鐘,主要是與大家分享我的學習過程。
不懂 canvas 的同學,請先學習:Canvas 畫布
相關幾何知識
鐘面是一個圓,主要包含每個小時數字、以及刻度,它們的位置座標應該如何計算呢?
從上圖很容易得到:
- x = r * cos(角度)
- y = r * sin(角度)
由於 Math
物件裡面的 sin()
和 cos()
方法使用的是弧度,所以需要進行轉換
但是,在這次的使用中,實際上並沒有用到,因為鐘面刻度等都是等比例劃分的,只要將 2 PI 除以刻度數等就可以得到相應弧度。
時針、分針、秒針,都是直線通過旋轉一定的角度得到
畫鐘面函式
使用 canvas 畫布,首先都應該先獲取它的繪圖上下文環境。
var canvas = document.getElementById('clock');
var cxt = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
var r = width / 2;
獲取了 canvas 元素的寬高,同時定義半徑 r 為最大的半徑,即寬度的一半。
function drawBg() {
//重置原點
cxt.save();
cxt.translate(r,r);
// 畫時鐘外圈
cxt.beginPath();
cxt.arc(0, 0, r - 5, 0, 2*Math.PI, true);
cxt.lineWidth = 8;
cxt.stroke();
//畫小時數
var hour = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2];
hour.forEach(function(num,i){
var rad = 2 * Math.PI / 12 * i;
var x = Math.cos(rad) * (r - 30);
var y = Math.sin(rad) * (r - 30);
cxt.font = "18px sans-serif"
cxt.textAlign = "center";
cxt.textBaseline = "middle";
cxt.fillText(num, x, y);
});
// 畫刻度
for (var i = 0; i < 60; i++) {
var rad = 2 * Math.PI / 60 * i;
var x = Math.cos(rad) * (r - 18);
var y = Math.sin(rad) * (r - 18);
cxt.beginPath();
if(i%5 == 0){
cxt.fillStyle = "#000";
cxt.arc(x, y, 2, 0, 2*Math.PI, true);
}
else {
cxt.fillStyle = "#bbb";
cxt.arc(x, y, 2, 0, 2*Math.PI, true);
}
cxt.fill();
}
}
重點:
- cxt.save()
這裡儲存了原來的原點位置,是為了在清除 canvas 的時候方便呼叫 cxt.clearRect()
方法
cxt.translate(r,r)
,將原點放置在 (r,r)位置,因為所有的刻度數等都是圍繞同心圓來進行的,原點放置在圓心上,是為了方便計算位置座標。注意,使用了這個方法,後面的繪圖都會基於這個原點繪製注意每畫一個圖形,都應該保持開啟一條新的路徑,避免畫筆等的重複
小時數字從 3 畫起,並按順時針畫,是因為 canvas 的座標系,向右為 x 正軸,向下為 y 正軸,為避免
sin()
和cos()
的正負值與x
、y
的正負值不對應。cxt.textAlign = "center"
和cxt.textBaseline = "middle"
,這是文字的對齊方法,不設定將會導致小時數字的偏移
畫時針
// 畫時針
function drawHour(hour, minute) {
cxt.save();
var rad = 2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * minute / 60;
cxt.beginPath();
cxt.rotate(rad);
cxt.moveTo(0, 15);
cxt.lineTo(0, -r/2);
cxt.lineWidth = 5;
cxt.lineCap = "round";
cxt.stroke();
cxt.restore();
}
重點:
- 由於每次都旋轉都會影響後面的繪圖,所以要在這裡儲存繪圖環境,在繪製時針結束後,重置回到原來的繪圖環境
- 千萬不要忘記,時針的旋轉要受到分鐘數的影響
畫分針、秒針、中心點函式
// 畫分針
function drawMinute(minute) {
cxt.save();
var rad = 2 * Math.PI / 60 * minute;
cxt.beginPath();
cxt.rotate(rad);
cxt.moveTo(0, 18);
cxt.lineTo(0, -r + 40);
cxt.lineWidth = 3;
cxt.lineCap = "round";
cxt.stroke();
cxt.restore();
}
// 畫秒針
function drawSecond(second) {
cxt.save();
var rad = 2 * Math.PI / 60 * second;
cxt.beginPath();
cxt.rotate(rad);
cxt.moveTo(0, 25);
cxt.lineTo(2, 25);
cxt.lineTo(-2, 25);
cxt.lineTo(-1, -r + 25);
cxt.lineTo(1, -r + 25);
cxt.lineTo(2, 25);
cxt.lineWidth = 1;
cxt.fillStyle = "#f00";
cxt.fill();
cxt.restore();
}
// 畫中心點
function drawDot() {
cxt.beginPath();
cxt.arc(0, 0, 4, 0, 2*Math.PI,true);
cxt.fillStyle = "#fff";
cxt.fill();
}
重點
:
- 儲存繪圖環境,同上面的時針
繪製真實時間
// 繪製真實時間
function draw() {
cxt.clearRect(0, 0, width, height);
var now = new Date();
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();
drawBg();
drawHour(hour,minute);
drawMinute(minute);
drawSecond(second);
drawDot();
cxt.restore();
}
draw();
setInterval(function(){
draw();
},1000);
重點:
- cxt.restore()
這裡重置的是畫鐘面的時候儲存的繪圖環境,目的是將原點重置回預設,才能使用清除矩形區域方法
cxt.clearRect(0, 0, width, height)
清除 canvas 區域,然後進行重新繪製。因為每次畫的時針、分針等,都會保留,所以要清空呼叫
setInterval()
方法之前,應該呼叫一次繪製,否則會出現延遲一秒的展現
優化
現在的時針是基於 200px
的正方形繪製的。那麼,如果將寬高變大,會出現什麼情況?
600px:
時針會變醜,大小不成比例,也就是失真。
那麼怎樣才能不失真呢?
由於時鐘是一個正方形區域,是一個圓的體現,那麼所有有關大小隻需要與半徑或者寬度成比例,就可以實現。
我們先來計算它們的比例關係:
200 / width = 13 / length
也就是說,如果在 200px
的寬度下,表現為 13px
的大小,那麼在 width
下,應該表現為 length
大小。
即:length = 13 * width/200
可得比例關係就是:width/200
定義一個比例:
var rem = width / 200;
只要在後面的各個函式中,有關聯的大小乘以這個比例關係,就可以實現,不管多大寬度的時鐘,都能完美的展現。