整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          時鐘和倒計時:CSS 和 JavaScript 中的計時


          一天,我需要一個數字時鐘組件,所以我很快編寫了一個簡單的 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,它回答說:

          • Clock1使用該setInterval()方法創建一個每 1000 毫秒(1 秒)觸發一次的循環計時器。這是性能最低的方法,因為它為每次迭代創建一個新的計時器,這可能會增加大量的開銷。
          • Clock2使用該setTimeout()方法,創建一個在 1000 毫秒后觸發的計時器。這比clock1 性能更高,因為它不會為每次迭代創建新的計時器。然而,它的性能仍然不如clock3,因為瀏覽器必須等待計時器觸發才能更新文本內容。
          • Clock3使用該requestAnimationFrame()方法,這是請求瀏覽器重繪的更有效方法。瀏覽器只會在必要時重新繪制元素,這可以節省大量的 CPU 時間。

          好吧,我決定使用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

          繪制鐘面內外兩個框

          繪制大小刻度和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 編譯通過)


          主站蜘蛛池模板: 日韩高清一区二区三区不卡| 在线观看午夜亚洲一区| 精品免费久久久久国产一区| 国产亚洲情侣一区二区无码AV| 一区二区三区在线观看视频| 日韩一区二区在线观看视频| 福利国产微拍广场一区视频在线| 中文字幕亚洲一区| 日韩av片无码一区二区三区不卡 | 中文字幕精品亚洲无线码一区| 精品无码人妻一区二区三区品| 国产精品亚洲产品一区二区三区 | 国产免费一区二区三区不卡| 在线成人一区二区| 久久综合亚洲色一区二区三区| 精品一区二区高清在线观看| 亚洲香蕉久久一区二区| 久久一本一区二区三区| 久久精品一区二区三区不卡| 国产成人精品无人区一区| 一区二区三区午夜| 久久国产香蕉一区精品| 亚洲国产精品一区二区九九| 天天爽夜夜爽人人爽一区二区| 在线精品一区二区三区电影| 日韩在线一区二区三区视频| 免费精品一区二区三区在线观看| 男人的天堂av亚洲一区2区 | 性色av闺蜜一区二区三区| 伦精品一区二区三区视频| 亚洲av色香蕉一区二区三区蜜桃| 国产高清一区二区三区 | 国产无码一区二区在线| 国产在线观看一区二区三区精品| 中文字幕一区二区三区视频在线 | 91一区二区三区| 亚洲国产精品综合一区在线| 日本一区二三区好的精华液| 丰满岳乱妇一区二区三区| 文中字幕一区二区三区视频播放 | 国产精品一区二区久久沈樵|