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;

只要在后面的各个函数中,有关联的大小乘以这个比例关系,就可以实现,不管多大宽度的时钟,都能完美的展现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • html: js: var dom = $("#clock"); var ctx = dom[0].getCont...
    后简1994阅读 262评论 0 1
  • 一:canvas简介 1.1什么是canvas? ①:canvas是HTML5提供的一种新标签 ②:HTML5 ...
    GreenHand1阅读 4,606评论 2 32
  • 神奇且强大的canvas 一.Canvas的基本介绍 1.什么是Canvas 定义:是HTML5提供的一种新标签,...
    Ainy尘世繁花终凋落阅读 10,383评论 1 18
  • 一、canvas简介 1.1 什么是canvas?(了解) 是HTML5提供的一种新标签 Canvas是一个矩形区...
    Looog阅读 3,858评论 3 40
  • Cnavas绘制时钟 背景图的绘制(大圆、数字、小圆点),掌握基础知识:圆的绘制(arc方法),关于圆的弧度的计算...
    Iris_mao阅读 2,432评论 7 26