iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 2
0
Modern Web

新手也能懂的JS30系列 第 2

JS30-Day2-JS and CSS Clock

Day2-課題內容

進入JS30的第二天,今天我們要利用Javascript中的方法以及CSS屬性,在頁面上做出一個會轉動的虛擬時鐘。[1]
實作連結一
實作連結二

取得時間

首先我們需要取得時間這筆資料。而在Javascript中,可以使用new Date()這個方法,來取得目前我們電腦中的時間物件,並且利用getter來取得所需的詳細資料,例如年、月、日、時、分、秒等:

var time = new Date();
var currentHour = time.getHours();
var currentMinute = time.getMinutes();
var currentSecond = time.getSeconds();
console.log(time); //Thu Dec 21 2017 10:19:24 GMT+0800 (CST)
console.log(currentHour); //10
console.log(currentMiute); //19
console.log(currentSecond);  //24

這邊我們只取得時鐘上需要的時針、分針與秒針所需的資料,在下方參考連結附上關於new Date()物件的MDN文件,各位讀者可以嘗試看看,將取得的時間物件,轉換成想要的資料形式,或者將頁面中的時間做修改,這些都是可以辦得到的。[2]

指針角度

取得時間資料之後,我們就可以開始將時間,轉換成各指針在時鐘上的對應角度:

  1. 每1小時,時針移動一格,因此每1小時時針轉動360/12度。
  2. 每1分鐘,分針移動一格,因此每1分鐘分針轉動360/60度。
  3. 每1秒鐘,秒針移動一格,因此每1秒鐘秒針轉動360/60度。

在作者提供的草稿當中,一開始三根指針都是指向9點鐘的方向,所以我們需要加上90度,將這些指針做歸零的動作,不然指針的位置將無法對應到正確的時間。此外還需加上分針與時針的偏移,因此我們得到各指針角度的內容如下:

var hourAngle = currentHour * 360/12 + currentMinute* 360/(12*60) + 90 ;
var minAngle = currentMinute * 360/60 + currentSecond *360/(60*60) + 90;
var secondAngle = currentSecond * 360/60 + 90;

CSS Transform()屬性

完成目前時間下每個指針所需對應的旋轉角度後,再來就是讓各個指針旋轉。此時就要用到CSS中的transform()這個屬性,透過這個屬性,我們可以將指定的元素,做2D或是3D的變形,而在此章節中我們將只會用到2D變形。

首先,我們要先定位旋轉的原點,如果我們沒有設定的話,會因為原點的X軸以及Y軸座標default設定為50%,發現物體繞著自己的中心旋轉,因此我們需要設定transform-origin這個屬性,透過將X軸原點設定在元素最右邊,也就是寬度100%,Y軸原點則設定為最高度的一半,也就是50%,我們的三個指針便可以對著時鐘中心點旋轉:

//在hand element中 加入transform-origin屬性:
.hand {transform-origin: 100% 50%;}

指定旋轉的原點之後,我們便可以加上transform中的另一個屬性rotate(),讓指定的元素,繞著原點旋轉一定角度:

{transform:ratate(90deg)}

了解以上兩種transform的屬性設定之後,便可以建立一個函式,讓頁面開啟時執行該函式,並透過element.style.cssProperty,將我們指定的元素,加上我們想要的CSS屬性,如此一來各指針便能轉到對應的角度:

function getAngle() {
    hourHand.style.transform = `rotate(${hourAngle}deg)`;
    minHand.style.transform = `rotate(${minAngle}deg)`;
    secHand.style.transform = `rotate(${secondAngle}deg)`;
};
getAngle();

可以看到在函式getAngle中,我們依序給時針、分針及秒針,加上transform屬性並給予其對應的rotate角度,並且在一開啟時便執行該函式。如此一來,當開啟頁面時,時鐘便會指出當下的時刻。

在transform屬性中還包含著許多功能,以下列出其他transform 2D變形的相關屬性:

  1. translate(X軸位置,Y軸位置):將物件移到距離原點座標(X軸位置,Y軸位置)的位置。這邊X軸位置向右為正,Y軸位置則向下為正。
  2. scale(X軸倍率,Y軸倍率):將物件沿著X軸或Y軸縮放,但只限於外觀的放大或縮小,並不會因為大小改變而影響element在頁面上的配置。
  3. skew(x角度,y角度):將物件沿著X軸或Y軸傾斜,看起來有漂浮在頁面上的效果,但為較少使用的CSS屬性。

這邊提供一個參考網站,可以直接在裡面修改各種2D變形的屬性,進而看到不同transform屬性在頁面上所產生的效果,可以對各個屬性有更進一步的認識。[3]

setInterval()方法

我們已經完成時鐘的初始設定,再來我們要讓各個指針隨著時間動起來。時鐘每個指針每秒會旋轉一定角度,因此最簡單的方式便是使用setInterval()這個方法。透過這個方法,我們可以指定某函式並指派其呼叫週期:

//每經過一定時間,執行一次函式。
setInterval(函式,時間/*毫秒*/)

當setInterval()方法存在時,他會持續呼叫所指定的函式,直到呼叫clearInterval()來終止或者關閉頁面。
而這邊有一個類似的方法,setTimeout()。這個方法可以讓我們設定多久時間後,執行一次指定的函式。

//經過指定時間後,只執行一次函式。
setTimeout(函式,時間/*毫秒*/)

讓時鐘轉動

了解setInterval()這個方法之後,我們便可以透過每秒呼叫一次函式,讓指針隨著時間轉動。我們在這邊使用的方法,是將各指針每秒所旋轉的角度,逐次加上,因此函式的內容如下:

function addAngle() {
    hourAngle = hourAngle + 360 / (12*60*60);
    minAngle = minAngle + 360 / (60*60);
    secondAngle = secondAngle + 360 / 60;
                     
    hourHand.style.transform = `rotate(${hourAngle}deg)`;
    minHand.style.transform = `rotate(${minAngle}deg)`;
    secHand.style.transform = `rotate(${secondAngle}deg)`;
};

setInterval(addAngle, 1000);

完成以上的設定之後,當頁面開啟時,函式getAngle()被呼叫,並將指針旋轉至對應角度,然後隨著setInterval()每秒呼叫一次函式addAngle(),使指針逐次增加旋轉角度,如此一來我們的虛擬時鐘就像裝上了電池,動起來了!

CSS Transition屬性

如果我們想更進一步,讓指針的行為更生活化,例如持續旋轉或者產生抖呢?這時我們就要來認識一下transition這個CSS屬性。透過這個屬性,我們可以將元素CSS屬性的轉變過程轉為動畫,而不只是單一從原本的外觀直接變成另一種外觀。
Transition中總共包含以下4種子屬性:

  1. transition-property:指定做變換的CSS屬性。
  2. transition-duration:變換過程的總時間。
  3. transition-delay:延遲多久後開始變換。
  4. transition-timing-function:定義變換時的速度曲線。

此4種子屬性也可寫成單一行transition屬性:

transition: property duration timing-function delay;

Transition-timing-function

前三個屬性我們都能從字面上了解其功能,但最後一個timing-function,似乎就比較難理解。
一般來說,會以貝茲曲線來解釋transition-timing-function,我們可以將其理解為時間對位置的關係,曲線中的X軸為過場時間,Y軸為過場動畫的位置,因此斜率就可以解釋成過場動畫速度。
此屬性中又提供了以下6種條件,大家可以透過參考網站,玩看看不同的條件下的過場行為 [4]:

  1. Linear:
    其貝茲曲線,可以看到為筆直的一條線,速度(斜率)上沒有任何的變化,因此過場動畫將以等速度開始到完成。

  2. Ease:
    其貝茲曲線一開始斜率無變化,然後斜率開始增加,最後斜率又漸趨平緩。因此過場動畫剛開始會短暫等速前進,然後加速,最後緩慢減速完成。

  3. Ease-in:
    其斜率由一開始平緩到後面急劇上升,所以過場動畫將一開始速度緩慢但後面劇烈加速來完成。

  4. Ease-out:
    此屬性為Ease-in的相反,斜率從剛開始急劇上升然後逐漸平緩,所以所以過場動畫將一開始劇烈加速但後面緩面減速來完成。

  5. Ease-in-out:
    此曲線前半段為Ease-in,後半段為Ease-out,過場動畫將會呈現先加速後減速的效果。

  6. 貝茲曲線(cubic-bezier):
    透過直接給定貝茲曲線的設定值,來設定這條曲線的行為。

cubic-bezier (A1, A2, B1, B2);


x軸: 時間
y軸: 距離

利用貝茲曲線,我們可以設定想要的效果,甚至可以使這條曲線超出這個cubic-bezier區域,而達到過場動畫超出的起點與終點範圍的效果。這邊有提供一個範例,讀者可以點擊下方的連結,來看到透過調整貝茲曲線達到的特殊效果。[5]

秒針顯示效果

模擬秒針的行為,在這邊我們提供兩個例子:

  1. 秒針連續旋轉:
    要讓秒針的行為呈現連續旋轉,首先我們要讓transform的動畫時間長短與秒針更換角度的時間相同,因此我們先將transition-duration設定為1秒。此外,秒針的行為是等速度前進,所以後面加上transition-timing-function:linear這個屬性:
.second-hand {
    transition: transform 1s;
    transition-timing-function: linear;
};
  1. 秒針現實抖動:
    觀察實際的時鐘,我們會看到秒針跳到下一秒的瞬間,會先超出原本的位置,然後在回到位置上。要做到這樣的效果,就需要貝茲曲線的幫忙。利用將曲線拉出貝茲曲線框,可以讓秒針多偏離一些角度,再讓transition-duration的時間縮短,就可以做出秒針跳過去的瞬間,先多偏離原本的位置,再回到位置上的抖動效果:
.second-hand {
    transition: transform 0.1s;
    transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1);
} 

小插曲

在作者提供的原始Code當中,我們會發現秒針在59跳回0秒的時候,會出現一個神奇的情況。秒針會瞬間逆時針轉回0度再馬上轉回到目前的位置。這是因為作者在旋轉角度的寫法,是將當下的時間換算成角度,因此當秒針從59秒要跳回0秒的時候,transform的rotate角度會由接近360轉回接近0度,以致於逆時針旋轉的產生。而在我們的code當中,因為旋轉角度是用每秒增加的方式加上去,因此不會有此現象的產生,各位讀者如果是依照JS30作者的方法練習的話,可以嘗試參考我們文章中的設定內容,便可以消除秒針的在位置的小插曲。

總結

在今天的時鐘課題當中,我們學到以下的技能:

  1. 取得時間
  2. CSS3 Transform屬性
  3. setInterval()方法
  4. CSS3 Transition屬性

透過理解javascript中new Date()這個方法,以及CSS transform以及transition屬性後,就可以做出我們想像中的時鐘。此外,利用這兩種CSS屬性,可以在頁面上做出更多令人驚豔的動畫效果喔!以上是JS30第二天的分享心得,歡迎大家交流討論。

參考資料

  1. javascript30
  2. MDN-Date物件
  3. 2D Transform&Transition CSS演示
  4. cubic-bezier2
  5. cubic-bezier3

上一篇
JS30-Day1-Javascript Drum Kit
下一篇
JS30-Day3-CSS Variables
系列文
新手也能懂的JS3030
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言