马黑PHP整站系统

做一个canvas时钟(六)

位置: 首页 > 前端三套件[ 发布时间: 2024.3.26  作者: 马黑  阅读: 290 ]

到第五讲时,高颜值的时钟还不会走时,这是因为缺乏驱动。驱动将通过 requestAnimationFrame API接口来实现。上一讲里,我们已经将画时钟的所有细节集中放到了一个函数里,函数名叫 render(),我们把 requestAnimationFrame(render) 放入该函数内,函数自身只要从外部执行一次就会得到反复调用,直到手工停止它的执行或页面被关闭。代码结构如下:

//函数 :绘制时钟部件集合 let render = () => {     //这里是绘制时钟具体部件的代码集群     requestAnimationFrame(render); //请求关键帧动画从函数内部调用函数 };

requestAnimationFrame 其实也是一个定时器,它依据屏幕刷新率作为自己的执行频率,就是说,一秒钟里面它至少执行60次上下的任务,这取决于显示器的刷新频率的高低,频率越高,它执行任务的次数越多。

不过问题是,假如我们现在就将 requestAnimationFrame(render); 加入到函数 render() 中,情况和第五节的最终效果没什么两样,时钟还是没能走时。这又是为什么呢?请看三根指针的绘制代码:

draw_rect(0, -3, 90, 6, 0, 'lightgreen'); /* 时针 */ draw_rect(0, -2, 100, 4, 270 * Math.PI/180, 'lightgreen'); /* 分针 */ draw_rect(0, -1, 120, 2, 240 * Math.PI/180, 'lightgreen'); /* 秒针 */

秒针为例,它通过绘制矩形函数 draw_rect() 绘制出来,函数将画布坐标系移到了画布的中心,指针总是从圆心区域画起,向三点钟方向画一定宽高的矩形,requestAnimationFrame(render) 绘制秒钟就这么重复着画。要转动指针,实际上是转动画布坐标系,函数提供了一个 rad 参数,我们绘制的秒针传值是 240 * Math.PI/180 弧度,秒针定格在 11 点钟方向。这里,240是十一点钟方向的角度值,改变这个角度值,秒针的指向就会发生变化。所以,我们首先需要知道当下的秒数是多少,然后根据算法计算出这个秒数是多少度就可以将它替代 240 令秒针每一秒钟都指向正确的方向,秒钟就动起来了。

获得当下时间信息,需要用到JS的 Date 对象:通过声明一个变量将其实例化,就能从实例化变量中获得所需的年月日时分秒等具体时间信息:

// 将 Date 对象实例化为 now let now = new Date();   // 从 now 实例化对象中获取时间信息 let year = now.getFullYear(); //年 let month = now.getMonth() + 1; //月 加1的原因:getMonth() 返回 0~11 的数值 let date = now.getDate(); //日 let day = now.getDay(); // 星期几(返回 0~6 的数值) let hour = now.getHour(); //小时 let minute = now.getMinute(); //分钟 let second = now.getSecond(); //秒钟 let msec = now.getMilliseconds(); //毫秒

通过上面的时间信息获取方法,我们用 now.getSecond() 就能拿到当下的秒数。那么,秒数和角度又有什么关系呢?秒针指向刻度,而时钟上的每一个刻度向圆心的延长线都会和指向三点钟方向的X轴形成一定角度的夹角。理论上,每一秒钟的行进距离就是一个刻度的距离,亦即,它走的角度值为 6 度,那么,假设现在的秒数是 sec 秒,则角度为 sec * 6,为了精准,还需要考虑毫秒数,假设现在的毫秒数为 msec,依据毫秒和秒的关系,其所在度数为 msec * 0.36 / 1000,这个要加到秒钟的偏移度数中。我们来看代码:

/* 获取时间信息 */ let now = new Date();   //获取秒数和毫秒数 let sec = now.getSeconds(),     msec = now.getMilliseconds();   //转换秒数毫秒数为角度 let sDeg = sec * 6 + (msec * 0.36 / 1000);   //绘制秒针 draw_rect(0, -1, 120, 2, (sDeg - 90) * Math.PI/180, 'lightgreen');

其他指针同理获得角度,然后将角度转换成弧度作为传参调用 draw_rect 依次绘制出来。这些,统统加入到 render() 函数中来,连同时钟的其他所有元素的具体绘制封装在一起,render() 内部在最后通过 requestAnimationFrame 按屏幕的刷新率去驱动时钟。时钟的最终效果以及全部代码如下,代码中,尤其需要注意对时间信息和时针转动角度的处理,这可是本讲的核心:

<div style="margin-top: 20px; text-align: center;">     <canvas id="canv" width="300" height="300"></canvas> </div> <script>   /* 获取画笔 */ let ctx = canv.getContext('2d');   /* 函数 :绘制矩形(指针) */ let draw_rect = (x, y, w, h, rad, color) => { ctx.save(); ctx.fillStyle = color; ctx.translate(150,150); ctx.rotate(rad); ctx.fillRect(x,y,w,h); ctx.restore(); }; /* 函数 :绘制圆(环) */ let draw_circle = (x,y,r,lw,color1,color2) => { ctx.save(); ctx.fillStyle = color1; ctx.strokeStyle = color2; ctx.lineWidth = lw; ctx.beginPath(); ctx.arc(x,y,r,0,Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.restore(); }; /* 函数 :绘制文本 */ let draw_text = (txt,x,y,color,fontsize=18,b="bold") => {     ctx.save();     ctx.translate(150,150);     ctx.font = `${b} ${fontsize}px sans-serif`;     ctx.textAlign = 'center';     ctx.textBaseline="middle";     ctx.fillStyle = color;     ctx.fillText(txt,x,y);     ctx.restore(); };   let render = () => {     /* 获取时间 */     let now = new Date();     let hr = now.getHours() > 12 ? now.getHours() - 12 : now.getHours(),         min = now.getMinutes(),         sec = now.getSeconds(),         msec = now.getMilliseconds();     let hDeg = hr * 30 + (min * 6 / 12),         mDeg = min * 6 + (sec * 6 / 60),         sDeg = sec * 6 + (msec * 0.36 / 1000);         ctx.clearRect(0,0,300,300); /* 每一次绘制前都衔擦除画布 */     draw_circle(150,150,140,10,'tan','darkgreen'); /* 钟壳和钟面 */       /* 钟点 */     for(let i = 0; i < 12; i ++) {         let radian = Math.PI/180*(i*30-60);         let x = 115 * Math.cos(radian), y = 115 * Math.sin(radian);         draw_text(i+1, x, y, 'green');     }     /* 刻度 */     for(let i = 0; i < 60; i ++) {         let radian = Math.PI/180*(i*6);         let x = 150 + 130 * Math.cos(radian), y = 150 + 130 * Math.sin(radian);         draw_circle(x,y,1,2,'gray','gray');     }     /* 按一定次序绘制时钟各个元素 :确保指针、指针扣不会被遮挡 */     draw_text(`${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`,0,-50,'white', 15, 'normal'); /* 日期 */     draw_text(`星期${'日一二三四五六'.substr(now.getDay(),1)}`,0,-25,'white', 15, 'normal'); /* 星期 */     draw_text('HUACHAO',0,60,'gray'); /* Logo */     draw_rect(0, -3, 90, 6, (hDeg - 90) * Math.PI/180, 'lightgreen'); /* 时针 */     draw_rect(0, -2, 100, 4, (mDeg - 90) * Math.PI/180, 'lightgreen'); /* 分针 */     draw_rect(0, -1, 120, 2, (sDeg - 90) * Math.PI/180, 'lightgreen'); /* 秒针 */     draw_circle(150,150,6,6,'white','lightgreen'); /* 指针扣 */       requestAnimationFrame(render); };   render();   </script>

《做一个canvas时钟》的课程至此结束。整体课程可能颇为抽象,尽管我已经极尽所能讲的很通俗了。时钟牵扯到的知识面很广,仅canvas画布的内容就相当不好理解,而且很多朋友此前对此没有什么基础。不过事在人为,能够消化这六个讲义,对提升编程水平和做帖能力等等都会有潜移默化的促进作用,甚至对改变自己的工作、学习和生活态度也会有正面的影响。谢谢大家!

前一篇: 做一个canvas时钟(五)
下一篇: CANVAS画布图形合成模式演示

发表评论:

       

评论列表 [4条]

#4 | 了了 于 2024-3-26 20:20 发布: sDeg - 90这里的负90想明白了。因为sDeg角度是按0度是3点钟方向来的,-90才能真正的归零。。

#3 | 飞飞 于 2024-3-26 17:53 发布: 最后一半还没烧脑完成。。刚整了一个成品出来。。

#2 | 悄然 于 2024-3-26 13:08 发布: 原来是在画布上不停的画呀画,钟就不停的走呀走。。。这么宠大的计算量,还要计算的分毫不差,还方方面面都要照顾到,老师你你你装的是台电脑吧。

#1 | 了了 于 2024-3-26 13:05 发布: 看到draw_rect(0, -1, 120, 2, (sDeg - 90) * Math.PI/180, 'lightgreen');秒针绘制。。sDeg - 90秒针算出来的角度减去90,这会还没想通。。。午休去了,晚上再继续。。。

Copyright © 2023 All Right Reserved 马黑PHP文章管理整站系统v1.8
联系我们: gxblk@163.com