JS30 全攻略 第2天

前言

JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks、No Compilers、No Libraries、No Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。

另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。


本日目標

透過 JavaScript 的 Date 物件分別取得"時"、“分”、"秒"並計算出在圓上的相對應角度,最後搭配 CSS 的 transformtransition 屬性,製作出一個簡易的時鐘。


解析程式碼

HTML 部分

由最外層的"clock"部分包住內層的"clock-face"和其內部的"時針"、“分針”、“秒針”,形成一個完整的巢狀結構。

1
2
3
4
5
6
7
<div class="clock">
<div class="clock-face">
<div class="hand hour-hand"></div>
<div class="hand min-hand"></div>
<div class="hand second-hand"></div>
</div>
</div>

CSS 部分

首先,將物件 transform 的基準點,更改為最右端。接著,將所有指針都預先固定在12點鐘方向。最後,透過 transition 屬性還有 transition-timing-function屬性,分別調整 CSS animation 效果變化速度和做出指針移動時的彈跳效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
.hand {
width: 50%;
height: 6px;
background: black;
position: absolute;
top: 50%;

/*以下是影片中教學的部分*/
transform-origin: 100%; /*改變 transform 的 x-axis*/
transform: rotate(90deg); /*初始位置從12點鐘出發*/
transition: all 0.05s; /*調整 CSS animation 變動的速度*/
transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1);
}

補充說明1:

transform 預設是以物件中心作為平移、旋轉、縮放、傾斜時的基準點。詳細內容見此

補充說明2:

transition timing function,可用來定義轉場發生的時間曲線,以四個參數的貝茲曲線代表。詳細內容見此

JS 部分

首先,分別取得代表"時針"、“分針”、"秒針"的標籤。

1
2
3
4
/*JS*/
const secondHand = document.querySelector('.second-hand');
const minsHand = document.querySelector('.min-hand')
const hourHand = document.querySelector('.hour-hand')

建立 Date 物件,取得"時"、“分”、"秒"的資料,以此算出所應旋轉的角度(注意,角度必須加上早先設定的90度,才會是正確的),之後分別調整 CSS 的 transform 屬性。

最後用 setInterval() 方法,設定每1000毫秒(1秒)就執行 setDate() 方法一次,藉此動態改變 rotate 的值。

(時針、分針、秒針的原理都一樣,只是在角度計算上有所差異)

1
2
3
4
5
6
7
8
9
10
11
12
/*JS*/
function setDate(){
const now = new Date();

/*時針、分針、秒針的原理都一樣*/
const seconds = now.getSeconds();
const secondsDegrees = ((seconds/60)*360) + 90;/*旋轉的角度要加上預設的90度*/

secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
}

setInterval(setDate,1000)

分別設定完"時針"、“分針”、"秒針"後,setDate() 方法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*JS*/
function setDate(){
const now = new Date();

/*時針、分針、秒針的原理都一樣*/
const seconds = now.getSeconds()
const secondsDegrees = ((seconds/60)*360) + 90;/*旋轉的角度要加上預設的90度*/
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;

const mins = now.getMinutes();
const minsDegrees = ((mins/60)*360) + 90;
minsHand.style.transform = `rotate(${minsDegrees}deg)`;

const hours = now.getHours();
const hoursDegrees = ((hours/12)*360) + 90;
hourHand.style.transform = `rotate(${hoursDegrees}deg)`;
}

以上都完成後,一個簡單的時鐘就出現了。但仔細一看就會發現指針在某個時間點會突然倒轉一圈。

舉"秒針"為例,在59秒~0秒之間,數值上的角度會從444度變為90度(分針也是如此),整整倒轉354度接近一圈,這就解釋了為什麼指針會有突然倒轉的現象。

而我們可以分別記錄時針和分針所走的圈數,並將原來計算出的度數加上360度*圈數,解決指針倒轉的問題。

用 if 判斷到 0 秒(分)時,就將圈數加1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var secRound = 0;  /*紀錄秒針所走圈數*/
var minRound = 0; /*紀錄秒針所走圈數*/

function setDate(){
const now = new Date();

/*時針、分針、秒針的原理都一樣*/
const seconds = now.getSeconds()
if(seconds == 0){/*避免回彈*/
secRound += 1;
}
const secondsDegrees = ((seconds/60)*360) + 360*secRound + 90;/*旋轉的角度要加上預設的90度*/
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;

const mins = now.getMinutes();
if(mins == 0){/*避免回彈*/
minRound += 1;
}
const minsDegrees = ((mins/60)*360) + 360*minRound + 90;
minsHand.style.transform = `rotate(${minsDegrees}deg)`;

const hours = now.getHours();
const hoursDegrees = ((hours/12)*360) + 90;
hourHand.style.transform = `rotate(${hoursDegrees}deg)`;
}

最後的最後,我們還可以讓指針的位置更加精準。一般而言,秒針每走一格,分針應該跟著移動一點,同理時針也是如此。

對分針而言移動每過一分鐘移動6度,我們可以用 (秒數/60)*6,算出實際上每過一秒鐘,分針應該要跟著移動多少度。

對時針而言移動每過一小時移動30度,我們可以用 (分鐘數/60)*30,算出實際上每過一分鐘,時針應該要跟著移動多少度。

1
2
3
4
5
6
7
8
9
10
const mins = now.getMinutes();
if(mins == 0){/*避免回彈*/
minRound += 1;
}
const minsDegrees = ((mins/60)*360) + 360*minRound + ((seconds/60)*6) + 90;
minsHand.style.transform = `rotate(${minsDegrees}deg)`;

const hours = now.getHours();
const hoursDegrees = ((hours/12)*360) + ((mins/60)*30) + 90;
hourHand.style.transform = `rotate(${hoursDegrees}deg)`;

補充說明1:

rotate(${hoursDegrees}deg)ES6 Template literals 的寫法。詳細介紹 Template literals

範例網頁請按此

分享到