JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘
JavaScript總算入門了,複雜的就先不來了,今兒牛刀小試,先來一個系統同步的時鐘效果,只用到最簡單的獲取系統時間的函式。因為學習是需要正反饋的,否則總也看不到效果,難免失了深入下去的興趣。在之前的一篇專欄文章 《利用CSS3的animation step屬性實現wifi動畫》 的末尾,曾經做過一個時鐘,當時鑑於JS空白,最終雖然也實現了時鐘效果,但卻無法做到與系統時間同步。今次,就來做一個可與當前時間完全一致的時鐘,並一步步實現從簡陋到豪華的華麗變身。
1. 基礎版,先實現效果
為了避免複雜的時鐘圖層太多的干擾,所以先來一個最最基礎的版本,一個圓代表底盤,三個直線分別代表時針分針秒針,嗯,或者你可以稱之為極簡主義(MUJI風?Maybe)。既然是基礎圖形,那可以不用藉助Ai,直接手動碼出來就可以了。
DOM部分(以800*600的畫布為例,原點為(400,300))
<!--圓形底盤--> <circle class="base" cx="400" cy="300" r="200" /> <!--長度為180的秒針--> <line class="pointer" id="second" x1="400" y1="300" x2="400" y2="120"/> <!--長度為140的分針--> <line class="pointer" id="minute" x1="400" y1="300" x2="400" y2="160"/> <!--長度為80的時針--> <line class="pointer" id="hour" x1="400" y1="300" x2="400" y2="220"/> 複製程式碼
CSS部分
<style type="text/css"> /* 圓形底盤 */ .base{stroke:#000000;stroke-width:4; fill:#FFFFFF} /* 指標公用描邊樣式 */ .pointer{opacity:0.5;fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} /* 描邊粗為6的秒針 */ #second{stroke-width:6;} /* 描邊粗為12的分針 */ #minute{stroke-width:12;} /* 描邊粗為18的時針 */ #hour{stroke-width:18;} </style> 複製程式碼
自然,這個最終的樣子不是那麼美,現在所有的指標都指向一個初始位置,即零時。

2.獲取當前時間在SVG圖形中體現
在此篇文章之前,一直玩的都是SVG+CSS3動畫,那這裡要囉嗦一下了。SVG畢竟只是一種圖形繪製方法,支援CSS控制樣式,但和控制互動的JS並無半點關聯,既然要用JavaScript的函式及操作,所以,這裡要做的第一步,就是把上一步建立的SVG圖形像普通圖片一樣,整個一股腦兒的塞到html檔案的body部分中。但這裡SVG與普通圖片相比,最大的優勢是裡面的標籤元素可以被獲取到並改變,往下看便知。在實際應用的過程中,要把SVG檔案和JavaScript以引入的形式使用,這裡為了方便,暫時不做剝離。
<!DOCTYPE html> <html lang="en"> <svg> …… SVG部分 </svg> </html> 複製程式碼
獲取當前時間的函式很簡單
<script> var dt=new Date(); //獲取小時 var hour=dt.getHours(); //獲取分 var minute=dt.getMinutes(); //獲取秒 var second=dt.getSeconds(); </script> 複製程式碼
下面要做的是,把返回的時間在時鐘上正確的對映,也就是說把旋轉的度數與時間一一對應。360度對應12個小時,60分鐘,60秒,就相應得出1hour→30度,1minute→6度,1second→6度的關係。為了方便使用,我把以上JS改了一下,直接改成度數值。
//小時對應的旋轉度數 var hourDeg=dt.getHours()*30; //分鐘對應的旋轉度數 var minuteDeg=dt.getMinutes()*6; //秒對應的旋轉度數 var secondDeg=dt.getSeconds()*6; 複製程式碼
CSS3裡支援基本的旋轉變形的屬性,transform:rotate(相應度數),所以這裡定義一個函式來給指標增加類樣式,旋轉的度數為上面獲取到的當前時間對應的旋轉度數。
//定義同步時間的函式 function syn(){ //給時針追加旋轉變形的類樣式 $("hour").style.cssText="transform:rotate("+hourDeg+"deg);"; //給分針追加旋轉變形的類樣式 $("minute").style.cssText="transform:rotate("+minuteDeg+"deg);"; //給秒針追加旋轉變形的類樣式 $("second").style.cssText="transform:rotate("+secondDeg+"deg);"; } 複製程式碼
這裡還有一個和旋轉變形關係極為密切的屬性,旋轉的原點 transform-origin ,因為是三種指標的共用屬性值,所以我把旋轉原點的定義 transform-origin:400px 300px 直接追加到了pointer類樣式中。
為了測試是否好用,我讓系統的時間暫時在視窗顯示。

當呼叫這個函式時,可以看到當前時間與SVG圖形中的時針分針秒針是完全對應的。
3.讓時鐘不停歇的動起來
其實上面的效果實現後,會一丟丟javascript的小夥伴已經知道了如何讓時鐘不停歇的動起來了。那就是定時器功能。只要設定每秒觸發一次定時器就可以了。
//定義獲取當前時間的函式 function timeId(){ var dt=new Date(); var hourDeg=dt.getHours()*30; var minuteDeg=dt.getMinutes()*6; var secondDeg=dt.getSeconds()*6; $("hour").style.cssText="transform:rotate("+hourDeg+"deg);"; $("minute").style.cssText="transform:rotate("+minuteDeg+"deg);"; $("second").style.cssText="transform:rotate("+secondDeg+"deg);"; } //定時器啟動前先呼叫一次函式 timeId(); //定義每秒觸發一次的定時器 setInterval(timeId, 1000); 複製程式碼

這樣的話,就得到了一枚醜醜的,但是可以與當前時間同步的時鐘。 但是,這裡有個小問題, 因為返回的系統時間是整數值,對於秒針而言,一格格的跳著走沒什麼問題,但對於分針尤其是時針而言,到了整點再跳一格,顯然是與現實世界中的時鐘不符的。 比如08:59:59在下一秒09:00:00時,時針會從8大幅度的跳向9。因為我的錶盤沒有指標,所以看上去並不是那麼明顯。但從擷取的GIF中可以看到明顯的分針從42跳到43的效果。
以時針為例,解決思路很簡單,只需要把分鐘返回的數值換算成時針相應的度數,與時針對應的度數相加即可。60minutes→1hour→30度,即1minute→0.5度。 每1分鐘,時針增加0.5度 。同理, 每1秒鐘,分針增加0.1度 。於是我把原來定義的時針和分針的旋轉變形角度,改成了下面這種
//時針度數修正,增加分針對應的度數 var hourDeg=dt.getHours()*30 + dt.getMinutes()*0.5; //分針度數修正,增加秒針對應的度數 var minuteDeg=dt.getMinutes()*6+dt.getSeconds()*0.1; 複製程式碼
按接近整點截了一段動圖,看一下效果

4.扔掉定時器,使用animation屬性
不要急不要急,雖然上面完成了一個完全同步的時鐘效果,但我們知道CSS3的 animation 屬性是很強大的,結合 @keyframes 動畫規則定義,是可以實現旋轉效果的。在上面的實現思路中,因為每一秒完成一次旋轉變形,因此並沒有用到animation屬性,下面就來試一試,如果不用定時器,如何實現這種效果。
先來看一下,如果是普通的旋轉動畫,不考慮動態的起始位置的話,定義的秒針的動畫規則及animation屬性的方法如下:
/*定義動畫規則,旋轉360度*/ @keyframes second{ 0%{transform:rotate(0deg)} 100%{transform:rotate(360deg)} } /*定義秒針的旋轉動畫屬性,360度需要時間60秒*/ .second{animation:second linear 60s infinite} 複製程式碼
這裡只需要解決一個問題,就是 0%關鍵幀對應的初始旋轉角度的問題 。0%的確認後,100%的只需要增加360度即可。問題難就難在javascript並不能把變數值傳進去來改變CSS的屬性,但好在可以通過javascrpt重寫CSS屬性值。這裡實現的思路有些麻煩,我貼上後逐句解釋。
以秒針為例,DOM結構中,增加類屬性的定義
<line class="pointer second" id="second" x1="400" y1="300" x2="400" y2="120"/>
CSS樣式中僅保留second動畫屬性的定義
.second{animation:second linear 60s infinite}
但動畫規則@keyframes通過以下方式實現:
- javascript建立一個新的CSS樣式標籤,<style type="text/css">……</style>
- 把規則作為內容寫入新的樣式標籤中
- 把新的樣式標籤追加進去
//建立新的CSS style標籤 var style = document.createElement("style"); style.type = "text/css"; //建立可以接受變數引數的動畫規則 //初始關鍵幀為分針對應的角度 //結束關鍵幀為分針對應的角度 var keyFrames = "@keyframes second {\ 0% {transform: rotate("+secondDeg+"deg);}\ 100%{transform:rotate("+(secondDeg+360)+"deg)}}"; //把動畫規則的內容寫入新建立的style標籤中 style.innerHTML = keyFrames; //把新的style標籤作為svg的子元素追加進去 document.getElementsByTagName("svg")[0].appendChild(style); 複製程式碼
現在,只觀察秒針,看看這種方法是不是可行。 f)

success!
通過這種方法,成功的把動態動畫規則寫入了CSS樣式中。秒針的沒有問題,那麼分針和時針就是水到渠成的問題了。這裡為了簡化程式碼,封裝了一個函式,把 動畫規則的名稱 和 初始的度數作為該函式的引數。
//建立一個新的CSS style標籤 var style = document.createElement("style"); style.type = "text/css"; //定義一個新的style標籤內容的函式,動畫規則名稱和初始度數作為引數 function newKeyFrames(name,degree){ var keyFrames = "@keyframes "+name+" {\ 0% {transform: rotate("+degree+"deg);}\ 100%{transform:rotate("+(degree+360)+"deg)}}"; //因為是新的style標籤內容中不斷增加新定義的動畫規則,所以改成+= style.innerHTML += keyFrames; } //時針的動畫規則名稱和初始旋轉度數作為引數傳入 newKeyFrames("hour",hourDeg); //分針的動畫規則名稱和初始旋轉度數作為引數傳入 newKeyFrames("minute",minuteDeg); //秒針的動畫規則名稱和初始旋轉度數作為引數傳入 newKeyFrames("second",secondDeg); //把全部增加動畫規則後的style樣式標籤追加到svg元素中 document.getElementsByTagName("svg")[0].appendChild(style); 複製程式碼
CSS部分,需要分別增加動畫屬性的類的定義。
/*秒針每旋轉360度,需要60秒*/ .second{animation:second linear 60sinfinite;} /*分針每旋轉360度,需要3600秒*/ .minute{animation:minute linear 3600sinfinite;} /*時針每旋轉360度,需要216000秒*/ .hour{animation:hour linear 216000sinfinite;} 複製程式碼
DOM結構中記得把類樣式附加上。
<line class="pointer second" id="second" /> <line class="pointer minute" id="minute" /> <line class="pointer hour" id="hour" /> 複製程式碼
可以看一下最終的效果了

似乎還有哪裡有點問題,秒針的跳幀效果!
/*把線性linear去掉,改成steps(60) 即一個動畫週期60秒跳幀為60*/ .second{animation:second steps(60) 60sinfinite;} 複製程式碼

無懈可擊!codepen附上原始碼 base template of SVG+javascript+CSS3 clock
顯而易見的是,第二種方法看上去並沒有第一種那麼簡單,甚至可以說比較繞,那麼為什麼這裡用了很多篇幅來探索這種方法的可行性?是因為這次的案例剛好是個時鐘,適合用定時器而已,而大多數情況下,我們需要一種可接受變數作為動畫規則引數的方法來解決問題,so。
5.以上是骨骼,以下才是靈魂
最難的部分已經完成,既然動畫效果出來了,那麼在此模板的基礎上,可以做一些絢爛的效果了。(友情提示,建議在下面這些動效原始碼的基礎上進行修改,有詳細註釋,可以直接用圖層元素去替換)至於在哪個模板的基礎上修改?全憑個人喜好,因為這個定時器是單執行緒的,不會吃記憶體,所以在這個案例中,暫時在這個模板的基礎上替換。
<!DOCTYPE html> <!--重點注意:!!!!Ai匯出之前一定要把指標都放置初始零點的位置--> <html lang="en"> <svg> <style type="text/css"> /* Ai匯出時生成的樣式 每次使用時需要替換*/ .st0{} …… /* 旋轉原點的值根據不同的底圖需要重新定義 */ .pointer{transform-origin: } </style> <!--在進行圖層排序時,遵循底圖-時針-分針-秒針-覆蓋層(如果有的話)的規律--> <g id="base"> 底圖對應的路徑 </g> <g class="pointer" id="hour"> 時針對應的路徑 </g> <gclass="pointer" id="minute"> 分針對應的路徑 </g> <g class="pointer" id="second"> 秒針對應的路徑 </g> <g id="overlay"> 覆蓋層對應的路徑 </g> </svg> <script> // 因為沒有用jQuery,因此封裝了一個最基本的函式 function $(id){ var idValue=document.getElementById(id); return idValue; } </script> <script> //定義獲取當前時間轉換成旋轉變形角度的函式 function timeId(){ var dt=new Date(); //時針度數修正,增加分針對應的度數 var hourDeg=dt.getHours()*30 + dt.getMinutes()*0.5; //分針度數修正,增加秒針對應的度數 var minuteDeg=dt.getMinutes()*6+dt.getSeconds()*0.1; var secondDeg=dt.getSeconds()*6; //給時針追加旋轉變形類樣式 $("hour").style.cssText="transform:rotate("+hourDeg+"deg);"; //給分針追加旋轉變形類樣式 $("minute").style.cssText="transform:rotate("+minuteDeg+"deg);"; //給秒針追加旋轉變形類樣式 $("second").style.cssText="transform:rotate("+secondDeg+"deg);"; } // 定時器觸發前先執行一次函式 timeId(); // 設定每1000ms觸發一次的定時器 setInterval(timeId, 1000); </script> </html> 複製程式碼
喜歡clock?ok!比如這種不帶刻度的簡易風codepen連結

以下均為各種無聊的炫技系列,懶得看可直接略過,無妨無妨。
光禿禿的底盤太單調?來個簡易刻度怎樣?codepen連結
不怕麻煩的全數字刻度codepen連結

喜歡手錶,說換就換,不就是替換一下的事情嘛codepen連結

太老土?那看看這種iWatch風格是不是你的菜

好了,不玩兒了,萬變不離其宗。快過年了,最後來個中洋結合的,招財貓配金玉滿堂金燦燦大福字的時鐘。(我知道是豬年,但是招財豬,那個豬蹄子揮起來太有喜感,so)
關於招財貓的貓爪前後擺動可以參考我以前的一篇專欄, 《無立體,不動畫,CSS3 3D 動畫屬性入門》 很基礎的3D屬性而已。只是因為是平面的,所以那個手臂的擺動會有些怪怪的。

祝各位新年鴻運當頭,即使網際網路寒冬再冷,也要笑著走下去。就醬。