iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Modern Web

拾起 Canvas,人人都是大藝術家!系列 第 12

第 12 幅 - 動畫練習:實作 Apple Watch Series 8 時鐘動畫

  • 分享至 

  • xImage
  •  

最近蘋果出了 Apple Watch Series 8,看了我牙癢癢,於是咬緊牙關,把他的錶面臨摹出來了(?)用 Canvas 畫時鐘的教學有很多,今天也參考了很多前輩大大還有 Mdn 的文件教學,當然如果大家有覺得怎麼寫可以更好~歡迎底下留言跟我分享。這個錶面就是我們今天臨摹的模特兒,看仔細了嗎?那我們就開始吧!

三步實作美美的時鐘動畫

Alt Text

Step 1. 拆解圖像,分為靜與動

在做動畫的時後我們可以先把要畫出來的圖像分為「靜止」與「動態」的兩種。靜止的例如時鐘的圓底、刻度、數字等,會動的則有時針、分針、秒針。靜止的圖像通常比較簡單一點,這次我們先依序完成錶面、刻度與數字。刻度的部分是經由畫線、旋轉並透過 for 迴圈完成重複的繪圖。

// 1 分刻度
  ctx.save();
  ctx.lineWidth = 3;
  for (let i = 0; i < 60; i++) {
    if (i % 5 !== 0) {
      ctx.beginPath();
      ctx.moveTo(117, 0);
      ctx.lineTo(120, 0);
      ctx.stroke();
    }
    ctx.rotate(Math.PI / 30);
  }
  ctx.restore();

// 5 分刻度
  ctx.save();
  for (let i = 0; i < 12; i++) {
    ctx.beginPath();
    ctx.rotate(Math.PI / 6);
    ctx.moveTo(115, 0);
    ctx.lineTo(120, 0);
    ctx.stroke();
  }
  ctx.restore();

Step 2. 時鐘數字:透過三角函數繪製位置

嘿嘿嘿該來的還是來了,繪製小時時竟然出現了 cos 和 sin!我們例如三角函式來計算每一個數字的坐標位置,這個公式可以拆解成先將角度轉換成弧度,再用三角函式來算出x, y 距離,這裡的 θ 是 30 度也是每一個數字之間間隔的角度。

https://ithelp.ithome.com.tw/upload/images/20220927/201306302sGIG9BblK.jpg

// 小時數字
const hourNum = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1];
  ctx.save();
  ctx.beginPath();
  ctx.font = "25px sans-serif";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillStyle = "black";
  ctx.rotate(Math.PI / 2);
	for (let i = 0; i < hourNum.length; i++) {
    let x = 95 * Math.cos(((i * 30 - 60) * Math.PI) / 180);
    let y = 95 * Math.sin(((i * 30 - 60) * Math.PI) / 180);
    ctx.fillText(hourNum[i], x, y);
  }
  }
  ctx.restore();

Step 3. 每一次繪的圖都要有「差異」

剩下的會動的部分主要可以拆解為秒針、分針與時針,他們的差異主要就是「選轉角度變化」,我們可以使用 rotate() 的 API 去控制旋轉的速率。這邊分針和秒針為例,我們可以把旋轉角度可以拆成 60 步。

秒針最好理解是每秒轉一格,ctx.rotate((sec * Math.PI) / 30) 的 30 就代表了 1/60 個圓。而分針是每 60 秒轉一格,我們每次抓的旋轉角度是「整數的分鐘」加上「不到一分鐘的秒數」,也就是下方 ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec) 的由來。

如果忘記 Rotate 後面的角度設定方法可以回去看看這篇文章唷: 第 6 幅 - 圖形應用:旋轉!變形!我閉著眼

  const sec = now.getSeconds();
  const min = now.getMinutes();
  const hr = now.getHours() % 12;

  // 分針
  ctx.save();
  ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec);
  ctx.lineWidth = 14;
  ctx.beginPath();
  ctx.moveTo(30, 0);
  ctx.lineTo(112, 0);
  ctx.strokeStyle = "black";
  ctx.stroke();
  ctx.restore();
  ctx.beginPath();
  ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
  ctx.fill();

  // 秒針
  ctx.save();
  ctx.rotate((sec * Math.PI) / 30);
  ctx.strokeStyle = "#E41515";
  ctx.fillStyle = "white";
  ctx.lineWidth = 3;
  ctx.beginPath();
  ctx.moveTo(-30, 0);
  ctx.lineTo(105, 0);
  ctx.stroke();
  ctx.beginPath();
  ctx.fillStyle = "black";
  ctx.arc(0, 0, 5, 0, Math.PI * 2, true);
  ctx.fill();
  ctx.restore();

最後,當所有動畫都繪製完成後,我們可以透過 requestAnimationFrame() 這個 API 來呼叫動畫函式。requestAnimationFrame() 主要的功能為

向瀏覽器請求在下次重繪前呼叫這個動畫函式。callback 的次數通常落在每秒 60 次,當頁面處於背景或隱藏狀態時,多數的瀏覽器會暫停 requestAnimationFrame() 的呼叫。—— mdn

所以我們就在繪製的函式最後一行透過 requestAnimationFrame() 呼叫自己,這樣就可以完成我們的動畫更新機制。

// 動畫函式
const appleWatch = () => {

// 下次重繪前呼叫 appleWatch 動畫函式
window.requestAnimationFrame(appleWatch)
}
// 首次渲染
window.requestAnimationFrame(appleWatch)

恭喜你已經完成了時鐘,做出來之後這個 Canvas Tag 可以放在網頁專案的任何一處,讓你的網頁有獨一無二的時鐘唷,當然也可以進階應用在番茄鐘、碼表倒數計時等專案中,非常期待看到大家自己的創意運用。如果你喜歡這個練習,歡迎抖內我 Apple Watch 我是說歡迎留言跟我互動,我們明天見!

點我看完整時鐘程式碼

觀念參考文章:
https://www.796t.com/content/1546830379.html
https://ithelp.ithome.com.tw/articles/10198918
https://creativecoding.in/2021/05/14/來用可怕的三角函數做網頁吧-part2-科幻時鐘/

實作參考 mdn:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations
https://developer.mozilla.org/zh-TW/docs/Web/API/window/requestAnimationFrame

做時鐘做到快午夜豪可怕!嗚嗚希望我的一天有多一點時間QQ


上一篇
第 11 幅 - 動畫解析:用 Canvas 讓飛天小女警起飛
下一篇
第 13 幅 - 事件監聽:誰在偷聽?偷聽什麼?
系列文
拾起 Canvas,人人都是大藝術家!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言