H5-Canvas-Clock-时钟
2017-03-28 08:24
363 查看
Canvas 示例:时钟
先来看下最终效果图:星期天趁空隙实现了个简单的一个时钟,下面回顾下其实现原理,和遇到的问题,对 H5 这块还是个菜鸟,只有不断通过练习去熟悉了。
设计
凡是从构思开始,而不是盲目的去实现代码。设计图:
从上图中该时钟分为几个部分
画布,背景蓝色部分;
第一个圆:时钟边框,最外层 8 个像素的宽边框;
第二个圆:点圆圈,代表着时间划分,每两个点之间代表 12 分钟(60 / 5 = 12);
第三个圆:数字圆,上面依次显示
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2];数字,代表小时数;
三个线条,分别是:时针(
r * 0.5),分针(
r * 0.75),秒针(
r * 0.85);
实现
根据上面的设计图和部位划分,来逐步实现 UI。时钟模块文件:
clock.js
// clock.js function Clock(canvas) { this.canvas = canvas; this.width = this.canvas.offsetWidth; this.height = this.canvas.offsetHeight; this.rem = this.width / 200; this.ctx = this.canvas.getContext('2d'); this.r = this.width / 2; this.digits = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2]; this.dotCount = 60; this.cellRad = 2 * Math.PI / this.digits.length; }
上面定义了时钟构造函数,参数是一个画布对象,成员包含
this.canvas:缓存画布对象;
this.width:画布的实际宽度,这里选择让其变成成员,原因是后面会使用到画布宽高,这里创建时进行缓存,避免后面使用时才去计算;
this.height:画布实际高度,同上;
this.rem:相对画布的比例,参考值:
200px,这个成员可以应对画布的大小缩放情况,使其他元素相应的做出响应;
this.ctx:画布上下文;
this.r:外圆的半径;
this.digits:小时数字数组,用来显示在时钟表盘上的数字;
this.dotCount:定义时钟表盘上的点的数量;
this.cellRad:小时与小时数字之间的弧度,这里其实就是
30 度角的弧度;
原型函数列表:(事先定义好可能需要的函数)
Clock.prototype._context:获取画布上下文,实际上就是得到
this.ctx成员;
Clock.prototype.drawBg:绘制背景,也就是最外圈的时钟的边框圆;
Clock.prototype.drawDigits:绘制数字,根据
单位角度 * 小时数字索引,将数字绘制到相应的位置上,这里需要注意的是画布的起始位置默认是水平向右的位置开始,也就是 3 点钟方向;
Clock.prototype.drawDot:绘制点,分割成 60 个点,同样是根据每两个点之间的弧度来实现;
Clock.prototype.drawHourHand:绘制小时时针线;
Clock.prototype.drawMinuteHand:绘制分钟时针线;
Clock.prototype.drawSecondHand:绘制秒钟时针线;
Clock.prototype._drawHand:绘制时针线的统一函数,因为不管是小时,分钟,秒钟也好,最终都是画线条,因此有其共同点,不同点在于线的粗细,长短,和弧度,因此可统一函数接口,将不同点作为参数传入;
Clock.prototype.drawCenterDot:绘制三个时针线的中心空心点,让三条线在跳动的时候感觉像是被固定要中心点一样;
Clock.prototype.draw:对外的绘制接口;
Clock.prototype._update:将所有绘制接口放到这里面,然后每一秒调用一次去更新当前时间刷新 UI;
Clock.prototype.start:启动时钟计时;
所有准备工作都OK了,下面就可以进入具体的功能实现部分了。(事先设计好 API 是个很不错的编程思路)
时钟边框
时针边框的背景就是个具有边框的空心圆,原理是很简单的// clock.js Clock.prototype.drawBg = function () { var ctx = this._context(), // 获取画布上下文 lineWidth = 8, // 指定线的宽度 // 这个半径需要考虑到边框 r = this.r - lineWidth * this.rem / 2; // 重置画布原点至画布中心点,因为后面的绘制都需要以中心点为圆点 // 为了方便起见,这里直接将中心点作为原点是个不错的选择 ctx.translate(this.r, this.r); // 开始路径 ctx.beginPath(); // 指定线宽 ctx.lineWidth = lineWidth; // 参数分别对应:圆点(x:0, y:0),半径:r,起始弧度:0,结束弧度:2π ctx.arc(0, 0, r, 0, 2 * Math.PI); // 结束路径,这里需要提醒的一点,最好先成对把 beginPath 和 closePah 先好 // 避免遗漏关闭路径 ctx.closePath(); // 绘制边框,如果填充则需要使用 ctx.fill(); ctx.stroke(); };
这样时钟边框圆就绘制完成了,如下图:
点圆绘制
第二个圆:点圆的绘制,整个时钟会被分割成 60 份,包含 60 个点,也就是说两个小时数之间会有5个分割弧度如设计图中右下角部分的绿色线条;这个点圆的绘制原理是:根据弧度来计算每个点中心点的的具体坐标(x, y),如下图
圆分割成60个圆弧后,每个圆弧的弧度:
rad = 2 * Math.PI / 60;
比如:
第一个点:12点上 (0,r)
第二个点:
(x = r * cos(rad), y = r * sin(rad));
第三个点:
(x = r * cos(2 * rad), y = r * sin(2 * rad));
…
第N个点:
(x = r * cos((n - 1) * rad), y = r * sin((n - 1) * rad));
根据上面的结果,那么我们代码就很简单了
// clock.js Clock.prototype.drawDot = function () { var ctx = this._context(), delta = 18, r = this.r - delta * this.rem, dotRad = 2 * Math.PI / this.dotCount, x, y, i; // 遍历所有的点,获取每个点的坐标(x,y),以该坐标为圆点绘制 for (i = 0; i < this.dotCount; i++ ) { // 计算当前点弧度 rad = dotRad * i; // 根据弧度得到点坐标 x = r * Math.cos(rad); y = r * Math.sin(rad); ctx.beginPath(); // 以 (x, y)为圆点绘制,半径为 1 的小圆点,采用填充方式 // 这里 * this.rem,是应对画布缩放的时候点圆的大小等比例缩放 ctx.arc(x, y, 1 * this.rem, 0, 2 * Math.PI); ctx.closePath(); ctx.fillStyle = 'gray'; if ( i % 5 === 0 ) { // 小时数上的点,以黑色强调显示 ctx.fillStyle = 'black'; } ctx.fill(); } };
这样就完成了外圈圆,和中间点圆的绘制,效果图如下:
点圆的绘制,重点在于每个点的左边的位置,根据被分割的单元弧度和点的索引即可计算出该点的弧度;
数字圆的绘制
数字圆的绘制和点圆的绘制原理是一样的,只是被分割的单元弧度不一样而已,另外需要注意的点是,数字左边上的文字布局问题;先来看下代码:
// clock.js Clock.prototype.drawDigits = function () { var me = this, ctx = this._context(), delta = 30, r = this.r - delta * this.rem, rad = 0, x, y; this.digits.forEach(function (digit, index) { rad = me.cellRad * index; x = r * Math.sin(rad); y = r * Math.cos(rad); ctx.font = '18px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(digit + '', x, y); }); return this; }
这里绘制方式很简单,直接定位到点(x, y),使用
fillText就会在该点处直接填充内容,主要看下这里两个属性:
ctx.textAlign和
ctx.textBaseLine
ctx.textBaseLine:文本基线对齐
这个属性可以指定指定点上的文本基线对齐方式,默认:普通的字母基线(
alphabetic)
w3school 上的各种取值的基线对齐图:
可取值列表:
值 | 描述 |
---|---|
alphabetic | 普通的字母基线 |
top | em 方框的顶端 |
hanging | 悬挂基线,单词最高字母的顶部紧贴基线方式 |
middle | em 方框的正中 |
ideographic | 表意基线 |
bottom | em 方框底部 |
除了
ideographic这个没太理解之外,其他的都还好理解,而我们这里用到的就是
middle根据
em方框的正中对齐;
ctx.textAlign: 文本位置
这个相对就比较好理解了,看图
时针绘制(小时,分钟,秒钟)
把小时,分钟,秒钟时针放一起,因为它们三个的绘制函数是一样的,只是需要控制其不通的角度和样式/** * 绘制线条(时针,分针,秒针线) * @param {Number} rad 弧度,当前时间对应的弧度 * @param {Number} length 时间针的长度 * @param {String} style 时间针的填充样式 * @param {Number} width 时间针的宽度 * @return {[type]} [description] */ Clock.prototype._drawHand = function (rad, length, style, width) { var ctx = this._context(); ctx.save(); ctx.beginPath(); // 这个负责根据弧度转动指针,也是根据当前时间实时更新时针的关键 ctx.rotate(rad); // 线条两端样式,可取值:butt|round|square ctx.lineCap = 'round'; ctx.lineWidth = width; ctx.strokeStyle = style; // 这里使用了个技巧,让起点位置往后突出了 10 个像素 ctx.moveTo(0, 10 * this.rem); ctx.lineTo(0, -length); ctx.closePath(); ctx.stroke(); ctx.restore(); return this; }
接下来根据不同指针的弧度绘制线条
时针
Clock.prototype.drawHourHand = function ( hour, minute ) { var rad, cellRad; cellRad = 2 * Math.PI / this.digits.length; // 小时数加上分钟数的弧度,如果不设置分钟数弧度, // 时针只会指向特定的小时数位置 rad = cellRad * hour + minute / 60 * cellRad; this._drawHand(rad, this.r * 0.5, 'black', 5); return this; };
分针
Clock.prototype.drawMinuteHand = function ( minute ) { var rad = minute * (2 * Math.PI / this.dotCount); this._drawHand(rad, this.r * 0.6, 'black', 5); return this; };
秒针
Clock.prototype.drawSecondHand = function ( second ) { var rad = second * (2 * Math.PI / this.dotCount); this._drawHand(rad, this.r * 0.78, 'gray', 2); return this; };
加上中间圆点
Clock.prototype.drawCenterDot = function () { var ctx = this._context(), x, y; ctx.beginPath(); ctx.arc(0, 0, 2 * this.rem, 0, 2 * Math.PI); ctx.closePath(); ctx.fillStyle = 'white'; ctx.fill(); return this; }
最终效果:
到此时钟的 UI 界面算是完成了。
指针实时更新
动起来,这里就需要用到计时器,去每个一秒获取当前时间的时分秒,去刷新三个指针的位置更新画布函数:
Clock.prototype._update = function (h, m, s) { this.ctx.clearRect(0, 0, this.width, this.height); this.drawBg(); this.drawDigits(); this.drawDot(); this.drawHourHand(h, m); this.drawMinuteHand(m); this.drawSecondHand(s); this.drawCenterDot(); this.ctx.restore(); return this; }
注意点:
return this;之前的
this.ctx.restore();
还原状态,还记得绘制背景的
this.drawBg();里面我们将绘制的起点位置使用
ctx.translate(this.r, this.r);重新设置到了
(this.r, this.r)。
如果这里不进行还原,那么下一次重绘会在
(this.r, this.r)的基础上再去重设起点,会导致刷新的时钟不断沿右下 45 度角上不断延伸;
如果我们这里在重绘整个时钟之前进行还原,那么画布起点就会回到
(0, 0)也就画布左上角位置,这样才能正确进入下一个时钟循环进行绘制
this.ctx.clearRect(0, 0, this.width, this.height);清除画布
这一句的作用在于绘制下一个时钟之前,清除画布上的指定矩形区内的所有内容,如果不清除,会发生不断重叠的现象,如下图:
通过
this.ctx.clearRect通过这个清除函数,可以将画布内容清空,方便绘制下一个时钟,其实就是清空画布,重新绘制整个时钟
启动更新:
Clock.prototype.start = function () { var date = new Date(), hour, minute, second, me = this; // 得到当前时间的时分秒 hour = date.getHours(); minute = date.getMinutes(); second = date.getSeconds(); // 更新画布 this._update(hour, minute, second); // 每个一秒刷新一次,这里用到了 requestAnimationFrame 动画帧请求函数 // 对于这个函数还没搞透,为啥就比 setTimeout 更准确的问题 requestAnimationFrame(function () { me.start(); }, 1000); };
最终效果动态图
总结
一个小时钟实现,原理其实很简单,主要实现步骤绘制外圈
绘制点圈
绘制数字
绘制三个指针
最后设置定时器更新指针
涉及的属性:
属性名 | 描述 |
---|---|
lineWidth | 线条宽度 |
lineCap | 线条两端样式,butt, round, square |
font | 字体 |
textAlign | 文本水平对齐方式,right, left, end, start, center |
textBaseLine | 基线对齐方式,alphabetic, top, hanging, middle, ideographic, bottom |
绘图函数:
ctx.arc(x, y, r, startRad, endRad);:绘制圆或圆弧;
ctx.moveTo(x, y);和
ctc.lineTo(x, y);: 绘制线条;
状态函数:
ctx.save();
ctx.restore();
清空画布函数:
ctx.clearRect(x, y, w, h);
其他函数:
ctx.rotate(rad);: 旋转角度;
ctx.fill/fillStyle: 填充和填充样式;
ctx.stroke/strokeStyle:描边和描边样式;
相关文章推荐
- JS+H5 Canvas实现时钟效果
- 通过H5的新标签canvas做出一个时钟的全过程,希望对初学者有帮助
- h5 canvas绘制的时钟
- h5之canvas画时钟
- H5——canvas——动态时钟
- HTML5---canvas 指针时钟-clock
- 跨时钟域设计【一】——Slow to fast clock domain
- 小练一下canvas版简单时钟与css3版漂亮时钟
- canvas实现的时钟效果
- 漂亮的桌面概念时钟-XinBSConceptClock
- 帮助你生成超酷计时器和时钟效果的jQuery插件 - FlipClock.js
- 【分享】纯javascript时钟(Clock)控件
- 用H5中的Canvas等技术制作海报
- 计数器(Counter)、时钟(Clock)、告警器(Alarm)、定时器(Timer)
- android 时钟组件AnalogClock与Clock
- H5学习之8 canvas的运用3 填充线性渐变颜色
- 利用H5Canvas进行前端图片压缩再上传笔记
- canvas简单时钟
- JavaScript Canvas绘制圆形时钟效果
- 使用canvas 做动态时钟特效