JavaScript 2/30: JS & CSS Clock

ZoeeeZhang發表於2019-01-07

JavaScript30 為Wes Bos推出的一項為期30天的挑戰,旨在幫助人們用純JavaScript來實現效果,初學者若想在JS方面快速精進,不妨一試。

實現效果

利用JS及CSS模擬時鐘,時、分、秒針實時擺動,例如現在是14:36,時鐘效果如下:

JavaScript 2/30: JS & CSS Clock

檢視我的 Demo程式碼

解題思路

  • 獲取時、分、秒針;
  • 獲取當前時間;
  • 計算當前時間下,時、分、秒針分別所需旋轉角度;
  • 調整當前時間下,指標在錶盤的位置。

頁面基礎佈局

  <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特性帶來的誤差。

JavaScript 2/30: JS & CSS Clock

改進方法如下

第一種方法:

在發生跳頓的角度值處,將 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);
複製程式碼

相關文章