一天,我需要一個數字時鐘組件,所以我很快編寫了一個簡單的 JavaScript 方法:
function uiDigitalClock(node) {
const now = () => {
node.textContent = new Date().toLocaleTimeString()
requestAnimationFrame(now)
}
now()
}
我在某處讀到,這requestAnimationFrame應該比 更好的性能setInterval,但該方法一直困擾著我。
在最佳條件下,每秒requestAnimationFrame觸發60 次——這比數字時鐘需要的多 60 倍!
Date()每秒創建 60 次對象對性能來說并不好!
所以我嘗試了setInterval:
function interval(node){
return setInterval(() => node.textContent = new Date().toLocaleTimeString(), 1000)
}
同樣,代碼不多,視覺結果是相同的 - 并且每秒只有一個對象。 Date()
又怎樣呢setTimeout()?
function timeout(node) {
const now = () => {
node.textContent = new Date().toLocaleTimeString()
setTimeout(now, 1000)
}
now()
}
它也能得到類似的結果,但有令人討厭的延遲(setImmediate()僅在 Node 中)
然后我決定修改初始版本,只每秒觸發一次,并requestAnimationFrame()返回時間戳:
function frame(node) {
let last = 0;
const render = now => {
if (!last || now - last >= 1000) {
last = now;
node.textContent = new Date().toLocaleTimeString();
}
requestAnimationFrame(render);
}
window.requestAnimationFrame(render);
}
當所有 3 個方法都運行時,我碰巧檢查了開發工具中的標記,并注意到我什至無法擴展節點,因為它們不斷刷新,并且看起來像舊的<blink>-tag!
仍然不知道該選擇哪種方法,我決定詢問 Google Bard,它回答說:
好吧,我決定使用clock3 ...但后來我突然想到:“ CSS 中的數字動畫怎么樣?使用@property?
Jhey不久前制作了一個純 CSS 秒表,所以我決定嘗試類似的東西。
@property --seconds {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
@property --minutes {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
@property --hours {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
然后,在<ol>-tag 中,我<li>為每個時間單位添加了一個 -tag。
要使用 - 聲明的值@property,您需要使用 CSS 計數器,因此幾秒鐘內它是:
.seconds {
animation: seconds 60s steps(60, end) infinite;
animation-delay: var(--delay-seconds, 0s);
counter-reset: seconds var(--seconds);
&::after { content: counter(seconds, decimal-leading-zero) ' '; }
}
要為秒設置動畫,需要一個關鍵幀:
@keyframes seconds {
from { --seconds: 0;}
to { --seconds: 60; }
}
對于幾分鐘來說,幾乎是一樣的,但是動畫花費了 60 倍的時間 (60 x 60 = 3600):
animation: minutes 3600s steps(60, end) infinite;
對于幾個小時,我們需要將該數字乘以 24:
animation: hours 86400s steps(24, end) infinite;
耶!我們有一個可以工作的 CSS 時鐘……但它只在午夜工作,因為小時、分鐘和秒都從0(零)開始。
那么該怎么辦?創建初始對象后,我可以輕松地從 JavaScript 更新屬性Date()。
但這樣動畫就會出錯,因為它們會運行相同的時間(每秒鐘 60 秒),即使實際的秒數小于該值。
我在 Twitter 上尋求幫助——幸運的是,Temani Afif 和 álvaro Montoro 回復了!解決方案是使用負數 animation-delay。
因此,使用一些 JavaScript 來設置當前時間并計算延遲:
const time = new Date();
const hours = time.getHours();
const minutes = time.getMinutes();
const seconds = time.getSeconds();
// Delays
const HOURS = -Math.abs((hours * 3600) + (minutes * 60) + seconds);
const MINS = -Math.abs((minutes * 60) + seconds);
const SECS = -Math.abs(seconds);
...我們可以更新之前指定的 CSS 屬性,例如:
node.style.setProperty(`--delay-seconds`, `${seconds}s`);
現在,我們有了一個可以工作的數字 CSS 時鐘——將其與此處的其他方法進行比較:
如果您在開發工具中檢查標記,您會發現 CSS 版本并未重寫 DOM 內容。
之后,我決定重新審視我的舊 Codepen,多語言倒計時,并制作一個純 CSS 版本:
locale如果您想要其他語言,您可以在 JS 代碼中使用:
但性能呢?CSS 可能不會像 JavaScript 那樣阻塞主線程,但我們能確定它使用 GPU 而不是 CPU 嗎?
有一個老技巧:
.useGpu {
transform: translateZ(0);
will-change: transform;
}
然后,在開發工具中,轉到“圖層”:
看到“倒計時”現在如何擁有自己的渲染層了嗎?不確定這是否仍然適用,但我猜添加也沒什么壞處。
當我離開瀏覽器選項卡并返回時,純 CSS 時鐘沒有出現任何問題。也許是我等的時間還不夠長吧!但如果您遇到任何問題,請使用此事件重新計算時鐘的延遲:
document.addEventListener('visibilitychange', () => {
if (!document.hidden) { ... }
})
作為獎勵 - 這是一個模擬時鐘,我不久前做了:
要自學前端開發,你要的學習資料到了-前端/JAVA/PHP學習交流群,新版css時鐘效果圖
<!DOCTYPE html>
<html>
<head>
<metahttp-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>RunJS</title>
<style>
.clock{
width:200px;
height:200px;
border-radius:100%;
position:relative;
background-image:url(
);
background-size:100%;
}
.line{
height:4px;
background-color:red;
margin-left:-15px;
margin-top:-2px;
}
.original{
position:absolute;
left:50%;
top:50%;
width:1px;
height:1px;
float:left;
}
.clock>.point{
position:absolute;
top:50%;
left:50%;
margin-left:-5px;
margin-top:-6px;
width:3px;
height:3px;
padding:5px;
background-color:red;
border-radius:13px;
}
.original.seconds{
-webkit-animation:rotate_origin60s linear infinite;
animation:rotate_origin60s linear infinite;
}
.original.seconds>.line{
background-color:red;
width:100px;
height:2px;
}
.original.minutes{
-webkit-animation:rotate_origin3600s linear infinite;
animation:rotate_origin3600s linear infinite;
}
.original.minutes>.line{
background-color:blue;
width:80px;
height:3px;
}
.original.hours{
-webkit-animation:rotate_origin86400s linear infinite;
animation:rotate_origin86400s linear infinite;
}
.original.hours>.line{
width:60px;
background-color:green;
}
@-webkit-keyframes rotate_origin{
from{
-webkit-transform:rotateZ(0deg);
}
to{
-webkit-transform:rotateZ(360deg);
}
}
@keyframes rotate_origin{
from{
transform:rotateZ(0deg);
}
to{
transform:rotateZ(360deg);
}
}
</style>
</head>
<body>
<divclass="clock">
<divclass="original hours">
<divclass="line"></div>
</div>
<divclass="original minutes">
<divclass="line"></div>
</div>
<divclass="original seconds">
<divclass="line"></div>
</div>
<divclass="point"></div>
</div>
</body>
</html>
不久用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在線文檔(https://docs.easyx.cn/zh-cn/intro)一邊開發。基本流程:
#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
繪制鐘面內外兩個框
由于大小刻度與指針有部分重疊,因此也要跟隨指針一起反復動態繪制。因此都將它們放入一個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);
}
//將各種動態繪圖代碼寫在此處
}
*請認真填寫需求信息,我們會在24小時內與您取得聯系。