前言
不管學(xué)習(xí)什么演侯,不動(dòng)手去做姿染,永遠(yuǎn)不能熟練掌握背亥。學(xué)習(xí)了 canvas API,會(huì)覺(jué)得只要按照直線悬赏、圓等畫(huà)法去畫(huà)狡汉,canvas 太簡(jiǎn)單了∶銎模可是盾戴,當(dāng)你真正去畫(huà)的時(shí)候,會(huì)遇到許多的問(wèn)題兵多。
下面介紹的是 canvas 時(shí)鐘尖啡,主要是與大家分享我的學(xué)習(xí)過(guò)程。
不懂 canvas 的同學(xué)剩膘,請(qǐng)先學(xué)習(xí):Canvas 畫(huà)布
一衅斩、相關(guān)幾何知識(shí)
鐘面是一個(gè)圓,主要包含每個(gè)小時(shí)數(shù)字怠褐、以及刻度畏梆,它們的位置坐標(biāo)應(yīng)該如何計(jì)算呢?
從上圖很容易得到:
- x = r * cos(角度)
- y = r * sin(角度)
由于 Math
對(duì)象里面的 sin()
和 cos()
方法使用的是弧度奈懒,所以需要進(jìn)行轉(zhuǎn)換
但是奠涌,在這次的使用中,實(shí)際上并沒(méi)有用到磷杏,因?yàn)殓娒婵潭鹊榷际堑缺壤齽澐值牧锍灰獙?2 PI 除以刻度數(shù)等就可以得到相應(yīng)弧度。
時(shí)針极祸、分針达皿、秒針天吓,都是直線通過(guò)旋轉(zhuǎn)一定的角度得到
二、畫(huà)鐘面函數(shù)
使用 canvas 畫(huà)布峦椰,首先都應(yīng)該先獲取它的繪圖上下文環(huán)境龄寞。
var canvas = document.getElementById('clock');
var cxt = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
var r = width / 2;
獲取了 canvas 元素的寬高,同時(shí)定義半徑 r 為最大的半徑汤功,即寬度的一半物邑。
function drawBg() {
//重置原點(diǎn)
cxt.save();
cxt.translate(r,r);
// 畫(huà)時(shí)鐘外圈
cxt.beginPath();
cxt.arc(0, 0, r - 5, 0, 2*Math.PI, true);
cxt.lineWidth = 8;
cxt.stroke();
//畫(huà)小時(shí)數(shù)
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);
});
// 畫(huà)刻度
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();
}
}
重點(diǎn):
cxt.save()
這里保存了原來(lái)的原點(diǎn)位置,是為了在清除 canvas 的時(shí)候方便調(diào)用cxt.clearRect()
方法cxt.translate(r,r)
滔金,將原點(diǎn)放置在 (r色解,r)位置,因?yàn)樗械目潭葦?shù)等都是圍繞同心圓來(lái)進(jìn)行的餐茵,原點(diǎn)放置在圓心上科阎,是為了方便計(jì)算位置坐標(biāo)。注意忿族,使用了這個(gè)方法锣笨,后面的繪圖都會(huì)基于這個(gè)原點(diǎn)繪制注意每畫(huà)一個(gè)圖形,都應(yīng)該保持開(kāi)啟一條新的路徑道批,避免畫(huà)筆等的重復(fù)
小時(shí)數(shù)字從 3 畫(huà)起错英,并按順時(shí)針畫(huà),是因?yàn)?canvas 的坐標(biāo)系隆豹,向右為 x 正軸椭岩,向下為 y 正軸,為避免
sin()
和cos()
的正負(fù)值與x
璃赡、y
的正負(fù)值不對(duì)應(yīng)判哥。cxt.textAlign = "center"
和cxt.textBaseline = "middle"
,這是文本的對(duì)齊方法碉考,不設(shè)置將會(huì)導(dǎo)致小時(shí)數(shù)字的偏移
三塌计、畫(huà)時(shí)針
// 畫(huà)時(shí)針
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();
}
重點(diǎn):
由于每次都旋轉(zhuǎn)都會(huì)影響后面的繪圖,所以要在這里保存繪圖環(huán)境豆励,在繪制時(shí)針結(jié)束后夺荒,重置回到原來(lái)的繪圖環(huán)境
千萬(wàn)不要忘記,時(shí)針的旋轉(zhuǎn)要受到分鐘數(shù)的影響
四良蒸、畫(huà)分針技扼、秒針、中心點(diǎn)函數(shù)
// 畫(huà)分針
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();
}
// 畫(huà)秒針
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();
}
// 畫(huà)中心點(diǎn)
function drawDot() {
cxt.beginPath();
cxt.arc(0, 0, 4, 0, 2*Math.PI,true);
cxt.fillStyle = "#fff";
cxt.fill();
}
重點(diǎn)
:
- 保存繪圖環(huán)境嫩痰,同上面的時(shí)針
五剿吻、繪制真實(shí)時(shí)間
// 繪制真實(shí)時(shí)間
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);
重點(diǎn):
cxt.restore()
這里重置的是畫(huà)鐘面的時(shí)候保存的繪圖環(huán)境,目的是將原點(diǎn)重置回默認(rèn)串纺,才能使用清除矩形區(qū)域方法cxt.clearRect(0, 0, width, height)
清除 canvas 區(qū)域丽旅,然后進(jìn)行重新繪制椰棘。因?yàn)槊看萎?huà)的時(shí)針、分針等榄笙,都會(huì)保留邪狞,所以要清空調(diào)用
setInterval()
方法之前,應(yīng)該調(diào)用一次繪制茅撞,否則會(huì)出現(xiàn)延遲一秒的展現(xiàn)
六帆卓、優(yōu)化
現(xiàn)在的時(shí)針是基于 200px
的正方形繪制的。那么米丘,如果將寬高變大剑令,會(huì)出現(xiàn)什么情況?
600px:
時(shí)針會(huì)變丑拄查,大小不成比例吁津,也就是失真。
那么怎樣才能不失真呢堕扶?
由于時(shí)鐘是一個(gè)正方形區(qū)域碍脏,是一個(gè)圓的體現(xiàn),那么所有有關(guān)大小只需要與半徑或者寬度成比例挣柬,就可以實(shí)現(xiàn)潮酒。
我們先來(lái)計(jì)算它們的比例關(guān)系:
200 / width = 13 / length
也就是說(shuō)睛挚,如果在 200px
的寬度下邪蛔,表現(xiàn)為 13px
的大小,那么在 width
下扎狱,應(yīng)該表現(xiàn)為 length
大小侧到。
即:length = 13 * width/200
可得比例關(guān)系就是:width/200
定義一個(gè)比例:
var rem = width / 200;
只要在后面的各個(gè)函數(shù)中,有關(guān)聯(lián)的大小乘以這個(gè)比例關(guān)系淤击,就可以實(shí)現(xiàn)匠抗,不管多大寬度的時(shí)鐘,都能完美的展現(xiàn)污抬。