這個時鐘包括兩個部分,動畫圓盤時鐘和數字時鐘。首先是使用超時調用setTimeout()方法做一個循環動畫的效果以顯示時間。先看數字時鐘的代碼,比較簡單,圓盤時鐘的做法也是模仿數字時鐘做的。
var ntimeoutId = setTimeout(ntimeOut,0);function ntimeOut() { clearTimeout(ntimeoutId); var now = new Date(); var hours = now.getHours().toString(), minutes = now.getMinutes().toString(), seconds = now.getSeconds().toString(); var time = hours+" : "+minutes+" : "+seconds; var timep = document.getElementById("time"); timep.innerHTML = time; ntimeoutId = setTimeout(ntimeOut, 1000); }
1、首先是設定一個超時調用以首次調用方法來顯示系統當前時間,時間之所以設置為0,是為了沒有延遲地顯示時間。
2、每次循環之前清除前一次的超時調用(為什么要這樣做,我也還不清楚???可能是為了內存性能相關,注釋了以后程序也能正常執行。)
3、取得當前系統時間,并按照一定的字符串格式保存。
4、為了動態顯示在頁面中,在Html頁面中定義了一個空的p元素,以存放該時間字符串。使用DOM操作將其添加到p元素中即可動態顯示。
5、通過不停循環超時調用,就可以動態顯示數字時鐘了, 同過開發者工具也可以看到p元素里的動態變化。
接下來就是制作圓盤時鐘動畫,圓盤和數值的刻畫都比較簡單,只要使用context.arc()方法和context.fillText()方法即可。下面是其源代碼:
context.beginPath(); context.restore(); context.translate(0,0); context.clearRect(0,0,300,300); //繪制時鐘內外邊框 context.arc(150,150,149,0,2 * Math.PI,false); context.moveTo(295,150); context.arc(150,150,145,0,2 * Math.PI,false); context.font = "bold 18px Arial"; context.textAlign = "center"; //繪制時鐘表盤數值 context.fillText("12",150,25); context.fillText("3",285,150); context.fillText("6",150,290); context.fillText("9",15,150); context.fillText("1",215,45); context.fillText("2",265,95); context.fillText("4",265,225); context.fillText("7",95,275); context.fillText("5",215,275); context.fillText("8",35,225); context.fillText("10",35,95); context.fillText("11",75,45); context.stroke(); context.closePath();
接下來就是指針的繪制了,指針的繪制中參考高程中的做法,使用變換操作context.translate()方法改變原點,再繪制路徑成為指針會方便很多。另一個繪制指針的難點是弧度的計算,當然這就是數學問題了。下面先看源代碼:
//繪制指針 context.save(); context.translate(150,150); //時針 context.moveTo(0,0); hour(hours); function hour(thour) { context.save(); var newhour = 0; if(thour>12) { newhour = thour-12; } else { newhour = thour; } context.rotate((2*Math.PI/12)*newhour); context.lineTo(0,-80); context.restore(); } //分針 context.moveTo(0,0); minute(minutes); function minute(tminute) { context.save(); context.rotate((2*Math.PI/12)*tminute/5); context.lineTo(0,-110); context.restore(); } //秒針 context.moveTo(0,0); second(seconds); function second(tsecond) { context.save(); context.fillStyle = "#fff"; context.rotate((2*Math.PI/12)*tsecond/5); context.lineTo(0,-120); context.restore(); } context.stroke();
在繪制指針中,每種指針都使用了函數來改變每次指針繪制的弧度來實現指針轉動的動畫效果。對于時針,則將二十四小時制轉化為十二小時制,每次轉動30°即可。分針和秒針則是轉為0到11進行轉動,簡單的數學問題,相信大家都是比我厲害的,當時我還糾結了一陣子。在每個函數中都有使用context.save()方法和context.restore()方法,是為了保存和復原初始化時的路徑,不然指針都要跑偏啦。
所有工作基本準備就緒了,接下來只要將鐘盤和指針都放在超時調用中即可,但仍然有些問題需要注意的,比如說鐘盤和指針的原點設置不同,要注意使用保存和復原來還原初始化時候的路徑,不然鐘盤和指針都要跑偏了。另外要注意的是,使用Canvas制作動畫,每次的動畫循環都是要清空畫布重新繪制,不然指針一直轉轉轉,轉成一朵花的樣子。
完整的圓盤時鐘代碼如下:
//顯示指針時間var drawing = document.getElementById("drawing");if(drawing.getContext) { var context = drawing.getContext("2d"); var rtimeoutId = setTimeout(roudClock,0); function roudClock() { clearTimeout(rtimeoutId); context.beginPath(); context.restore(); context.translate(0,0); context.clearRect(0,0,300,300); //繪制時鐘內外邊框 context.arc(150,150,149,0,2 * Math.PI,false); context.moveTo(295,150); context.arc(150,150,145,0,2 * Math.PI,false); context.font = "bold 18px Arial"; context.textAlign = "center"; //繪制時鐘表盤數值 context.fillText("12",150,25); context.fillText("3",285,150); context.fillText("6",150,290); context.fillText("9",15,150); context.fillText("1",215,45); context.fillText("2",265,95); context.fillText("4",265,225); context.fillText("7",95,275); context.fillText("5",215,275); context.fillText("8",35,225); context.fillText("10",35,95); context.fillText("11",75,45); context.stroke(); context.closePath(); var now = new Date(); var hours = now.getHours(), minutes = now.getMinutes(), seconds = now.getSeconds(); //繪制指針 context.save(); context.translate(150,150); //時針 context.moveTo(0,0); hour(hours); function hour(thour) { context.save(); var newhour = 0; if(thour>12) { newhour = thour-12; } else { newhour = thour; } context.rotate((2*Math.PI/12)*newhour); context.lineTo(0,-80); context.restore(); } //分針 context.moveTo(0,0); minute(minutes); function minute(tminute) { context.save(); context.rotate((2*Math.PI/12)*tminute/5); context.lineTo(0,-110); context.restore(); } //秒針 context.moveTo(0,0); second(seconds); function second(tsecond) { context.save(); context.fillStyle = "#fff"; context.rotate((2*Math.PI/12)*tsecond/5); context.lineTo(0,-120); context.restore(); } context.stroke(); context.restore(); context.translate(0,0); context.save(); rtimeoutId = setTimeout(roudClock,1000); } }
最后總結我在這次Demo的練習中遇到的幾點問題:
1、畫布重繪問題
在指針動畫循環的時候,前一個路徑都沒有方法清除,造成每一次循環都留下印記。嘗試了小范圍地使用clearRect()方法,結果發現只能在范圍內清除了表盤和數字的內容,指針依然會留下痕跡。后來通過搜索,得到的答案是使用Canvas制作動畫一定是要進行重繪的,在新繪制內容前要清空畫布內容,每一次的動畫變化都要清空一次。重繪的方法可以參考一下鏈接,我使用的是clearRect()方法清空整個畫布。
2、save()方法和restore()方法的使用
因為我的鐘盤和指針的原點設定不同,所以在進行重繪后,鐘盤的原點會改變為指針的原點,因此要利用save()方法和restore()方法改變。除了這個地方也有其他一些地方需要用到,適合操作以后改變了設定,但后續操作需要用原設定的情況。注意的是這兩個方法只會保存和恢復設定,而不是內容。
3、translate()方法的使用
translate()方法是屬于變換操作中的,可改變畫布的原點,默認畫布的原點在畫布的左上角。使用這個方法可以更輕易地繪制指針的路徑,當然還有一些其他需要用的地方,我還沒有接觸到。
4、setTimeout()方法的使用
使用超時調用是比間歇調用更好的方法,使用超時調用可以模擬間歇調用。通過在超時調用傳入的函數中再添加超時調用就可以很好地模仿循環。還可以在函數中根據一些條件限定循環的次數和時間。
5、對應當前時間,弧度的使用方法
這就是數學問題,我在這個問題糾結了好一會兒,沒有拐過彎來,明白其中的原理就不是技術上的難題了。
6、對于時間重復取得和重復使用方法的問題
對于代碼中還存在有重復代碼的情況,我還沒有想到更好的方法減少冗余。例如在獲取時間上,在兩個超時調用中均有重復定義,但若是把他們放在全局中,則沒有了動畫效果,只是顯示加載完成后的那個靜態時間。另外的是指針的函數設定中有重復的部分,是否可以合為一個函數方法再進行調用呢。
歡迎大家提出想法,對于不足的地方提出建議,一起來交流。
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com