JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

泱泱發表於2019-01-30

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;
複製程式碼

按接近整點截了一段動圖,看一下效果

修正後的時鐘效果
實現了平滑過渡。這種方法的實現,其實並沒有用到CSS3的動畫屬性,只用到了旋轉變形屬性。codepen附上全部原始碼 base template of SVG+javascript clock

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)

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘
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 60s  infinite;}
/*分針每旋轉360度,需要3600秒*/
.minute{animation:minute linear 3600s  infinite;}
/*時針每旋轉360度,需要216000秒*/
.hour{animation:hour linear 216000s  infinite;}
複製程式碼

DOM結構中記得把類樣式附加上。

<line class="pointer second" id="second" />
<line class="pointer minute" id="minute" />
<line class="pointer hour" id="hour" />
複製程式碼

可以看一下最終的效果了

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

似乎還有哪裡有點問題,秒針的跳幀效果!

/*把線性linear去掉,改成steps(60) 即一個動畫週期60秒跳幀為60*/
.second{animation:second steps(60) 60s  infinite;}
複製程式碼

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

無懈可擊!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>
<g  class="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連結

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

以下均為各種無聊的炫技系列,懶得看可直接略過,無妨無妨。

光禿禿的底盤太單調?來個簡易刻度怎樣?codepen連結

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

不怕麻煩的全數字刻度 codepen連結

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

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

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

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

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

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

JavaScript牛刀小試,結合CSS3動畫屬性來做一個系統時間同步的時鐘

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

相關文章