JavaScript30 為Wes Bos推出的一項為期30天的挑戰,旨在幫助人們用純JavaScript來實現效果,初學者若想在JS方面快速精進,不妨一試。
實現效果
利用JS及CSS模擬時鐘,時、分、秒針實時擺動,例如現在是14:36,時鐘效果如下:
解題思路
- 獲取時、分、秒針;
- 獲取當前時間;
- 計算當前時間下,時、分、秒針分別所需旋轉角度;
- 調整當前時間下,指標在錶盤的位置。
頁面基礎佈局
<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部分程式碼
html {
background: #018DED url(http://unsplash.it/1500/1000?image=881&blur=50);
background-size: cover;
font-family: `helvetica neue`;
text-align: center;
font-size: 10px;
}
body {
margin: 0;
/* font-size: 2rem; */
display: flex;
flex: 1;
min-height: 100vh;
/* 設定段落的最小高度 */
align-items: center;
/* 適用於flex容器,彈性盒子元素在該行的側軸(縱軸)上居中放置。 */
}
.clock {
width: 30rem;
height: 30rem;
border: 20px solid white;
border-radius: 50%;
/*向 div 元素新增圓角邊框:*/
margin: 50px auto;
position: relative;
padding: 2rem;
box-shadow: /*向div元素新增一個或多個陰影,逗號分隔*/
0 0 0 4px rgba(0, 0, 0, 0.1),
inset 0 0 0 3px #EFEFEF,
inset 0 0 10px black,
0 0 10px rgba(0, 0, 0, 0.2);
}
.clock-face {
position: relative;
width: 100%;
height: 100%;
transform: translateY(-3px);
/* account for the height of the clock hands */
}
.hand {
width: 50%;
height: 6px;
background: black;
position: absolute;
top: 50%;
transform-origin: 100%;/* 該屬性可以用來更改元素變形的原點,在這裡可設定旋轉的中心點,初始值為【50%,50%,0】。*/
/* transform: rotate(90deg); */
transition: all 0.05s;
transition-timing-function: ease-in-out;
/* 根據時間的推進去改變屬性值的變換速率。*/
}
複製程式碼
備註:
涉及的小知識點已經在註釋中提及,不展開解釋。有關CSS3 Transition
的疑問,詳見該網站。有關transform-origin
的疑問,可參見該網站屬性演示,可以幫助你更好理解。
JS部分程式碼
const HourHand = document.querySelector(`.hour-hand`);
const MinHand = document.querySelector(`.min-hand`);
const SecondHand = document.querySelector(`.second-hand`);
function setDate() {
const now = new Date();
const sec = now.getSeconds();
const secdeg = (sec / 60) * 360 + 90;
SecondHand.style.transform = `rotate(${secdeg}deg)`;
const min = now.getMinutes();
const mindeg = ((min / 60) * 360) + ((sec / 60) * 6 )+ 90;
MinHand.style.transform = `rotate(${mindeg}deg)`;
const hour = now.getHours();
const hourdeg = ((hour / 12) * 360) + ((min / 60) * 30) + 90;
HourHand.style.transform =`rotate(${hourdeg}deg)`;
}
setDate() ;
setInterval(setDate,1000);
複製程式碼
JS部分解析思路(以秒針為例)
- 獲取秒針節點
const SecondHand = document.querySelector(`.second-hand`);
複製程式碼
- 獲取當前時間為幾秒
const now = new Date();
const sec = now.getSeconds();
複製程式碼
- 計算當前時間下,秒針旋轉角度
const secdeg = (sec / 60) * 360 + 90;
複製程式碼
- 利用
transform: rotate(deg)
特性,調整秒針在表面內擺動位置
SecondHand.style.transform = `rotate(${secdeg}deg)`;
複製程式碼
- 設定定時器,每秒呼叫一次
setDate()
函式
setDate();
setInterval(setDate, 1000);
複製程式碼
延伸思考
觀察時鐘,我們會發現一個小bug,當秒針轉動一圈完畢,59秒至60秒時,會出現一次大回彈,角度值的變化為 444°-> 90°,逆時針回彈,0秒至1秒時,恢復順時針旋轉,角度值變化為90° ->96°,再恢復原狀,這是transition
特性帶來的誤差。
改進方法如下
第一種方法:
在發生跳頓的角度值處,將 CSS 的 transition
屬性去掉,逆時針回彈的過程將在瞬間完成,可忽略不計。
const HourHand = document.querySelector(`.hour-hand`);
const MinHand = document.querySelector(`.min-hand`);
const SecondHand = document.querySelector(`.second-hand`);
function setDate() {
const now = new Date();
const sec = now.getSeconds();
const secdeg = (sec / 60) * 360 + 90;
const min = now.getMinutes();
const mindeg = ((min / 60) * 360) + ((sec / 60) * 6) + 90;
const hour = now.getHours();
const hourdeg = ((hour / 12) * 360) + ((min / 60) * 30) + 90;
if (secdeg === 90) {
SecondHand.style.transition = `all 0s`
}
// 在指標即將出現大擺動的那一刻,去除transition屬性
else {
SecondHand.style.transition = `all 0.05s`
}
SecondHand.style.transform = `rotate(${secdeg}deg)`;
MinHand.style.transform = `rotate(${mindeg}deg)`;
HourHand.style.transform = `rotate(${hourdeg}deg)`;
}
setDate();
setInterval(setDate, 1000);
複製程式碼
第二種方法是:
解決思路是,按照之前的程式碼所寫,每秒都會new一個Date,如果讓秒針角度保持一直增長的狀態,而非每次重新計算,就不會出現這樣的問題了。
const HourHand = document.querySelector(`.hour-hand`);
const MinHand = document.querySelector(`.min-hand`);
const SecondHand = document.querySelector(`.second-hand`);
let secdeg = 0; let mindeg = 100; let hourdeg = 1;
function setDate() {
const now = new Date();
const sec = now.getSeconds();
secdeg = (sec / 60) * 360 + 90;
const min = now.getMinutes();
mindeg = ((min / 60) * 360) + ((sec / 60) * 6) + 90;
const hour = now.getHours();
hourdeg = ((hour / 12) * 360) + ((min / 60) * 30) + 90;}
function updateDate() {
secdeg += (1 / 60) * 360;
mindeg += ((1 / 60) / 60) * 360;
hourdeg += (((1 / 60) / 60) / 12);
SecondHand.style.transform = `rotate(${secdeg}deg)`;
MinHand.style.transform = `rotate(${mindeg}deg)`;
HourHand.style.transform = `rotate(${hourdeg}deg)`;
}
setDate();
setInterval(`updateDate()`, 1000);
複製程式碼