Canvas 畫(huà)時(shí)鐘

前言

不管學(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)污抬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汞贸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子印机,更是在濱河造成了極大的恐慌矢腻,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件射赛,死亡現(xiàn)場(chǎng)離奇詭異多柑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)楣责,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)竣灌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)聂沙,“玉大人,你說(shuō)我怎么就攤上這事初嘹〖昂海” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵屯烦,是天一觀的道長(zhǎng)豁生。 經(jīng)常有香客問(wèn)我,道長(zhǎng)漫贞,這世上最難降的妖魔是什么甸箱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮迅脐,結(jié)果婚禮上芍殖,老公的妹妹穿的比我還像新娘。我一直安慰自己谴蔑,他們只是感情好豌骏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著隐锭,像睡著了一般窃躲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钦睡,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天蒂窒,我揣著相機(jī)與錄音,去河邊找鬼荞怒。 笑死洒琢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的褐桌。 我是一名探鬼主播衰抑,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼荧嵌!你這毒婦竟也來(lái)了呛踊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啦撮,失蹤者是張志新(化名)和其女友劉穎谭网,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逻族,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜻底,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薄辅。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡要拂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出站楚,到底是詐尸還是另有隱情脱惰,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布窿春,位于F島的核電站拉一,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏旧乞。R本人自食惡果不足惜蔚润,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尺栖。 院中可真熱鬧嫡纠,春花似錦、人聲如沸延赌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挫以。三九已至者蠕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掐松,已是汗流浹背踱侣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甩栈,地道東北人泻仙。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓糕再,卻偏偏與公主長(zhǎng)得像量没,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子突想,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容