1. 程式人生 > >Canvas 畫時鐘

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() 的正負值與 xy的正負值不對應。

  • 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;

只要在後面的各個函式中,有關聯的大小乘以這個比例關係,就可以實現,不管多大寬度的時鐘,都能完美的展現。