前言:
现时咱们对“js实现电子时钟自动走动的效果”大致比较注重,各位老铁们都想要学习一些“js实现电子时钟自动走动的效果”的相关内容。那么小编同时在网络上网罗了一些关于“js实现电子时钟自动走动的效果””的相关知识,希望兄弟们能喜欢,朋友们快快来了解一下吧!前不久用JS+html<canvas>标签实现了简易时钟(见HTML使用Canvas绘制动画时钟),最近学习C/C++语言(话说怎么区分写的是c还是c++?),恰好看到一个有趣的绘图库EasyX,拿它来练练手,就先做个简易时钟看看吧。
EasyX Graphics Library 是针对 Visual C++ 的免费绘图库,支持 VC6.0 ~ VC2022。EasyX可以帮助 C/C++ 初学者快速上手图形和游戏编程。
比如,可以基于 EasyX 图形库很快的用几何图形画一个房子,或者一辆移动的小车,可以编写俄罗斯方块、贪吃蛇、黑白棋等小游戏,可以练习图形学的各种算法,等等。
简单了解了EasyX后,就可以开干了。本文开发环境是windows 11和Visual Studio 2022。首先打开vs2022并创建一个空的控制台项目,在项目里新建main.cpp,#include EasyX的头文件graphics.h。提示:由于EasyX是针对C++的绘图库,必须用cpp文件来开发编译,但是编码可以是c也可以是c++。
EasyX使用非常简单,可以一边阅读EasyX在线文档()一边开发。基本流程:
#include <graphics.h> // 引用图形库头文件int main(){ initgraph(600, 400); // 创建绘图窗口,大小为 600x400 像素 //绘图操作 closegraph(); // 关闭绘图窗口 return 0;}
下面基于以上基础代码逐步添加。
准备工作
由于EasyX默认情况下生成的绘图界面是黑色的背景,我们可以使用setbkcolor函数更改界面背景色为白色:
setbkcolor(WHITE);cleardevice(); //设置背景色后要清除设备才能生效setlinecolor(BLACK);//设置线条颜色(白底黑线)
在 EasyX 中,坐标分两种:物理坐标和逻辑坐标。
物理坐标是描述设备的坐标体系。
坐标原点在设备的左上角,X 轴向右为正,Y 轴向下为正,度量单位是像素。
坐标原点、坐标轴方向、缩放比例都不能改变。
逻辑坐标是在程序中用于绘图的坐标体系。
坐标默认的原点在窗口的左上角,X 轴向右为正,Y 轴向下为正,度量单位是点。
默认情况下,逻辑坐标与物理坐标是一一对应的,一个逻辑点等于一个物理像素。
默认原点(0,0)在绘图窗口的左上角,示例中为了方便绘制时钟,将原点修改为窗口的中心点:
//假设窗口大小为600x400,则中心点为(600/2,400/2)setorigin(300,200);//坐标原点设置成功后,(300,200)位置即为绘图坐标的原点(0,0)
为方便代码编写,先定义了几个常量:
#define W 800 //窗口宽度#define H 620 //窗口高度#define oY H/2 //调整坐标原点y为窗口高度的一半#define R 290 //时钟内圆半径#define PI 3.14#define HD PI/180 //因需多次计算弧度,提前算出部分公共值#define R_2 R/2绘制钟面内外框
时钟可以分成钟面外框、大小刻度和指针。由于指针指向是需要随时间动态变化,因此不能固定不变。而钟面外框是固定不变的,可以只绘制一次。(大小刻度也是固定不变的,但是由于与秒针行走路径有重叠,也要动态绘制)
示例中钟面外框绘制成两个大圆,都有一定的宽度。
绘制圆形的函数是void circle( int x, int y, int radius );参数分别是圆心的x、y坐标以及半径长度,如果要绘制宽度,可以使用setlinestyle()函数设置线条的形状和宽度:
setlinecolor(BLACK);//设置线条颜色setlinestyle(PS_SOLID, 10);//设置线条样式和宽度circle(0, 0, oY - 10); //绘制时钟外黑框//画出时钟的内框setlinestyle(PS_SOLID, 4);circle(0, 0, R);setlinestyle(PS_SOLID, 1);//绘制完钟面内外两个框后恢复线条宽度为1
绘制大小刻度和12个数字
由于大小刻度与指针有部分重叠,因此也要跟随指针一起反复动态绘制。因此都将它们放入一个while循环中。
示例中,大刻度画小实心圆,小刻度画短线条。画刻度和指针都需要定位坐标,需要用上数学计算,须先引入头文件math.h。具体的坐标计算方法可以自行度娘或看前一篇js+canvas绘制时钟,里面有简单的解释。
setfillcolor(BLACK);//大刻度实心小圆的填充色(黑色)double _x, _y;TCHAR s[3];settextcolor(BLACK);LOGFONT f;gettextstyle(&f);f.lfHeight = 36; //设置字体大小_tcscpy_s(f.lfFaceName, _T("serif")); //设置字体名称f.lfQuality = ANTIALIASED_QUALITY; //设置字体平滑效果settextstyle(&f);for (int j = 9, i = 0; i < 12; j++, i++) {//由于坐标0度指向刻度3,所以有针对性地修正一下 if (i > 2) { j = i - 3; } int _t = j * 30;//每个大格30度,用于下面的弧度计算 _x = cos(HD * _t) * (R - 5); _y = sin(HD * _t) * (R - 5);//计算大刻度的圆心坐标 fillcircle(_x, _y, 5);//绘制大刻度//开始绘制文字,先计算文字显示位置的矩形坐标 swprintf_s(s, _T("%d"), i == 0 ? 12 : i); RECT tr;//定义文字矩形结构 if (j == 10) {//修正部分矩形形状,j==10指向1时 tr.left = _x - 50; tr.top = _y; tr.right = _x; tr.bottom = _y + 50; }else if(j==4){//指向7时 tr.left = _x +50; tr.top = _y; tr.right = _x; tr.bottom = _y -50; }else if (j == 1) {//指向4时 tr.left = _x - 50; tr.top = _y - 50; tr.right = _x; tr.bottom = sin(HD * (5+_t)) * (R - 5); }else if( j == 7) {//指向10时 tr.left = _x+10; tr.top = sin(HD * (5 + _t)) * (R - 5); tr.right = _x+50; tr.bottom = _y+50; }else { tr.left = cos(HD * (_t - 5 < 0 ? 355 : _t - 5)) * (R); tr.top = sin(HD * (_t - 5 < 0 ? 355 : _t - 5)) * (R); tr.right = cos(HD * (_t + 5)) * (R - 70); tr.bottom = sin(HD * (_t + 5)) * (R - 70); } //简单地绘制12个数字,如果有更好的定位方式欢迎告知,谢谢。 drawtext(s, &tr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
//绘制60个小刻度setlinestyle(PS_SOLID | PS_ENDCAP_SQUARE, 3);double xx = 0;for (int j = 0, i = 0; i < 60; i++) { j = i - 15; if (j < 0) { j = 60 + j; } if (j % 5 == 0) { continue; } xx = HD * j * 6; line(cos(xx) * (R - 10), sin(xx) * (R - 10), cos(xx) * (R - 5), sin(xx) * (R - 5));}//绘制圆心fillcircle(0, 0, 8);
效果图:
绘制指针
指针有三种,时针粗短,分针适中,秒针细长,示例中将秒针绘制成红色以示区别。指针的位置是根据时间动态更新的,所以先要获取当前的系统时间,可以简单地引入time.h头文件。
//绘制时针,参数为当前时,分,秒void drawHp(int h,int m,int s) { h -= 3; if (h < 0) { h = 12 + h; } double hd = HD * h * 30 + PI/360 * m +PI/21600 * s; int _x = cos(hd) * (R_2+10); int _y = sin(hd) * (R_2+10); drawPt(_x, _y, 8);}//绘制分针,参数为当前分,秒void drawMp(int h,int s) { h -= 15; if (h < 0) { h = 60+h; } double hd = HD * (double)(h + s / static_cast<double>(60)) * 6; int _x = cos(hd) * (R_2+40); int _y = sin(hd) * (R_2+40); drawPt(_x, _y, 5);}//绘制秒针,参数为当前秒void drawSp(int h) { h -= 15; if (h < 0) { h = 60 + h; } double hd = HD * h * 6; int _x = cos(hd) * (R_2 + 120); int _y = sin(hd) * (R_2 + 120); drawPt(_x, _y, 2);}//根据指针x、y坐标真实绘制指针void drawPt(int x, int y,int w) { if (w == 2) { setlinecolor(RED); } setlinestyle(PS_SOLID | PS_JOIN_ROUND | PS_ENDCAP_ROUND, w); line(0, 0, x, y); setlinecolor(BLACK);}
调用方式:
time_t now = time(NULL);struct tm info;localtime_s(&info,&now);//获得本地时间int hour = info.tm_hour;int minute = info.tm_min;int second = info.tm_sec;drawHp(hour,minute,second);drawMp(minute,second);drawSp(second);
让我们看看效果如何。
哈哈,闹笑话了。由于我们在实时地?计算新的指针坐标并更新绘制新的指针位置,因此界面上绘制出了很多的秒针轨迹。怎么解决呢?方法肯定是有的,可以绘制前先清除设备(调用cleardevice()函数,将清除整个绘图窗口),我们这里不打算清除全部,只把钟面圆框内的部分清除:
clearcircle(0, 0, oY-25);//先清除内圆框之内的全部//然后再写动态绘图刻度和指针的代码
看看效果如何:
到此为止,一个简单的时钟已经绘制完成了。但是你可能发现有点小问题:界面有时会闪烁一下。这是由于动态更新指针位置并实时绘制出来造成的闪烁,可以使得经典的双缓冲技术来解决。EasyX很贴心地提供了这个技术,只需要三个函数:
BeginBatchDraw();while(true){ //反复的绘制操作 FlushBatchDraw();}EndBatchDraw();
就是这么简单。在我们的示例代码中加上它们就行了。
简单优化:节流
示例中将动态时间计算和实时更新绘图都放在了while循环中,计算机超强的计算速度下,每秒可将会计算非常多次,示例中时钟是按秒走的,我们可以采用节流思想,在一段时间的指定时间间隔内只执行一次回调函数,限制在一定时间内的执行次数,从而控制高频率触发的事件,避免过多的计算或操作影响性能。最简单的做法是一次绘图更新完成后,sleep一定时间。也可以根据系统时钟打点数计算fps:
const clock_t FPS = 1000 / 2;//每秒只执行两次clock_t startTime, freamTime;while (true) { //计算帧率 startTime = clock(); freamTime = clock() - startTime; if (freamTime < FPS) { Sleep(FPS - freamTime); } //将各种动态绘图代码写在此处}完整代码(Visual stdio 2022 编译通过)
#include<graphics.h>#include<math.h>#include<time.h>#define W 800 //窗口宽度#define H 620 //窗口高度#define oY H/2 //调整坐标原点y为窗口高度的一半#define R 290 //时钟内圆半径#define PI 3.14#define HD PI/180#define R_2 R/2//绘制时钟钟面void drawFace();//绘制时针void drawHp(int h,int m,int s);//绘制分针void drawMp(int h,int s);//绘制秒针void drawSp(int h);void drawPt(int x, int y,int w);int main() { initgraph(W, H,0); SetWindowText(GetHWnd(),_T("动态时钟")); //调整坐标原点为窗口中心,方便绘图 setorigin(W / 2, oY);//设置坐标原点,以窗口中心点当作坐标原点(0,0) setbkcolor(WHITE); cleardevice(); setlinecolor(BLACK);//设置线条颜色 setlinestyle(PS_SOLID, 10);//设置线条样式和宽度 circle(0, 0, oY - 10); //绘制时钟外黑框 //画出时钟的内框 setlinestyle(PS_SOLID, 4); circle(0, 0, R); setlinestyle(PS_SOLID, 1); const clock_t FPS = 1000 / 2;//每秒执行两次 clock_t startTime, freamTime; BeginBatchDraw();//开启双缓冲绘图功能,防止界面闪烁 while (true) { //计算帧率 startTime = clock(); freamTime = clock() - startTime; if (freamTime < FPS) { Sleep(FPS - freamTime); } clearcircle(0, 0, oY-25);//将需要动态绘制的部分清除掉 time_t now = time(NULL); struct tm info; localtime_s(&info,&now); int hour = info.tm_hour; int minute = info.tm_min; int second = info.tm_sec; drawFace(); drawHp(hour,minute,second); drawMp(minute,second); drawSp(second); FlushBatchDraw(); } EndBatchDraw(); closegraph(); return 0;}void drawHp(int h,int m,int s) { h -= 3; if (h < 0) { h = 12 + h; } double hd = HD * h * 30 + PI/360 * m +PI/21600 * s; int _x = cos(hd) * (R_2+10); int _y = sin(hd) * (R_2+10); drawPt(_x, _y, 8);}void drawMp(int h,int s) { h -= 15; if (h < 0) { h = 60+h; } double hd = HD * (double)(h + s / static_cast<double>(60)) * 6; int _x = cos(hd) * (R_2+40); int _y = sin(hd) * (R_2+40); drawPt(_x, _y, 5);}void drawSp(int h) { h -= 15; if (h < 0) { h = 60 + h; } double hd = HD * h * 6; int _x = cos(hd) * (R_2 + 120); int _y = sin(hd) * (R_2 + 120); drawPt(_x, _y, 2);}void drawPt(int x, int y,int w) { if (w == 2) { setlinecolor(RED); } setlinestyle(PS_SOLID | PS_JOIN_ROUND | PS_ENDCAP_ROUND, w); line(0, 0, x, y); setlinecolor(BLACK);}void drawFace() { //绘制12个大刻度,使用白色实心小圆 setfillcolor(BLACK); double _x, _y; TCHAR s[3]; settextcolor(BLACK); LOGFONT f; gettextstyle(&f); f.lfHeight = 36; //设置字体大小 _tcscpy_s(f.lfFaceName, _T("serif")); //设置字体名称 f.lfQuality = ANTIALIASED_QUALITY; //设置字体平滑效果 settextstyle(&f); for (int j = 9, i = 0; i < 12; j++, i++) { if (i > 2) { j = i - 3; } int _t = j * 30; _x = cos(HD * _t) * (R - 5); _y = sin(HD * _t) * (R - 5); fillcircle(_x, _y, 5);//绘制大刻度 swprintf_s(s, _T("%d"), i == 0 ? 12 : i); RECT tr;//定义文字位置 if (j == 10) {//1时 tr.left = _x - 50; tr.top = _y; tr.right = _x; tr.bottom = _y + 50; }else if(j==4){//7时 tr.left = _x +50; tr.top = _y; tr.right = _x; tr.bottom = _y -50; }else if (j == 1) {//4时 tr.left = _x - 50; tr.top = _y - 50; tr.right = _x; tr.bottom = sin(HD * (5+_t)) * (R - 5); }else if( j == 7) {//10时 tr.left = _x+10; tr.top = sin(HD * (5 + _t)) * (R - 5); tr.right = _x+50; tr.bottom = _y+50; }else { tr.left = cos(HD * (_t - 5 < 0 ? 355 : _t - 5)) * (R); tr.top = sin(HD * (_t - 5 < 0 ? 355 : _t - 5)) * (R); tr.right = cos(HD * (_t + 5)) * (R - 70); tr.bottom = sin(HD * (_t + 5)) * (R - 70); } drawtext(s, &tr, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } //绘制60个小刻度 setlinestyle(PS_SOLID | PS_ENDCAP_SQUARE, 3); double xx = 0; for (int j = 0, i = 0; i < 60; i++) { j = i - 15; if (j < 0) { j = 60 + j; } if (j % 5 == 0) { continue; } xx = HD * j * 6; line(cos(xx) * (R - 10), sin(xx) * (R - 10), cos(xx) * (R - 5), sin(xx) * (R - 5)); } //绘制圆心 fillcircle(0, 0, 8);}
标签: #js实现电子时钟自动走动的效果