不知道你們童年時候有沒有在手上畫手錶的經歷,剛好最近在看 canvas
,因而就誕生了這個高仿錶盤。 javascript
我這裏參照了 Apple Watch 中的這個錶盤:css
<div class="wrap"> <canvas id="canvas" width="400" height="400"></canvas> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.translate(200, 200); // 移動座標系中心點 // 繪製黑色底盤 ctx.beginPath(); ctx.moveTo(-100, -80); ctx.quadraticCurveTo(-100, -100, -80, -100); ctx.lineTo(80, -100); ctx.quadraticCurveTo(100, -100, 100, -80); ctx.lineTo(100, 80) ctx.quadraticCurveTo(100, 100, 80, 100); ctx.lineTo(-80, 100); ctx.quadraticCurveTo(-100, 100, -100, 80); ctx.closePath(); ctx.fillStyle = 'black'; ctx.fill(); </script>
translate
,跟 css
中的屬性類似,把後面繪製過程當中的座標系進行了平移,方便計算;ctx.arc
圓弧也能夠,以下示意圖右上角的點 cp 就是該位置貝塞爾曲線的控制點。
繪製日期文字:html
const now = moment(); ctx.textAlign = 'left'; ctx.fillStyle = '#ce4c50'; ctx.font = '11px "Helvetica Neue"'; ctx.textBaseline = 'middle'; ctx.fillText(now.format('D'), 15, 0); ctx.fillStyle = 'white'; ctx.fillText(now.format('ddd'), 33, 0);
繪製刻度:java
ctx.fillStyle = 'white'; ctx.font = '20px "Helvetica Neue"'; ctx.strokeStyle = 'white'; ctx.textAlign = 'center'; // 繪製圓盤刻度點和數字 for (let index = 60; index > 0; index -= 1) { if (index % 5 === 0) { ctx.lineWidth = 3 ctx.fillText(index / 5, 0, -70); } else { ctx.lineWidth = 1; } ctx.beginPath(); ctx.moveTo(0, -90); ctx.lineTo(0, -85); ctx.stroke(); ctx.rotate(- Math.PI * 2 * (1 / 60)); }
rotate
屬性,即繞中心點旋轉,須要刻畫 60 個刻度,一週的弧度爲 2 Math.PI ,每次旋轉 1 / 60 ,每 5* 格添加小時文字並加粗刻度線;
對指針的繪製,首先以原點爲中心繪製一個圓,對延伸出來的指針思考了兩種繪製方法:canvas
第一種:以 Y軸左半邊 爲例,點 (-2, 0) 爲起始點,以 cp1 、cp2 爲控制點,(-1, -12) 爲終點繪製三次貝塞爾曲線;函數
第二種:以 Y軸右半邊 爲例,直接從點 (1, -1) 繪製直線到 (1, -12) 。動畫
// 時針 const hour = new Path2D(); hour.arc(0, 0, 2, 0, Math.PI * 2); // hour.moveTo(-2, 0); // hour.bezierCurveTo(-2, -2, 0, -1, -1, -12); hour.moveTo(-1, -1); hour.lineTo(-1, -12) hour.lineTo(-2, -13); hour.lineTo(-2, -45); hour.arc(0, -47, 2, -Math.PI, 0); hour.lineTo(2, -13); // hour.lineTo(1, -12); // hour.bezierCurveTo(0, 1, 2, -2, 2, 0); hour.lineTo(1, -12); hour.lineTo(1, -1);
我把兩種都實現了一遍發現效果都差很少,大概是我繪製的圖形不夠大,看不出區別。spa
接下去須要把指針指向對應的時間,以 16點20分 爲例,咱們計算時針須要旋轉的角度:.net
const h = 16 % 12; // 錶盤上只有12大格 const m = 20; const hAngle = h / 12; // 這裏只計算旋轉角度佔一圈的比例,每小時 1/12 const mAngle = (m / 60) * (1 / 12); // 不滿1小時的部分,還須要乘上分鐘數在1小時中的比例 const angle = Math.PI * 2 * (hAngle + mAngle); // 最終須要旋轉的角度 ctx.rotate(angle); ctx.fill(hour); ctx.stroke(hour);
接下去在描述分針的時候咱們發現只是末端部分稍微長一點,咱們能夠選擇沿用時針的部分代碼,寫成這樣:指針
// 分針 const minute = new Path2D(hour); // 沿用代碼 minute.moveTo(-2, -45); minute.lineTo(-2, -82); minute.arc(0, -84, 2, -Math.PI, 0); minute.lineTo(2, -13); const minute = new Path2D(); // 不沿用代碼 minute.arc(0, 0, 2, 0, Math.PI * 2); minute.moveTo(-1, -1); minute.lineTo(-1, -12) minute.lineTo(-2, -13); minute.lineTo(-2, -82); minute.arc(0, -84, 2, -Math.PI, 0); minute.lineTo(2, -13); minute.lineTo(1, -12); minute.lineTo(1, -1);
值得注意的一點是:咱們在繪製完時針後,若是接下去直接計算角度繪製分針,上下文會以上次旋轉的角度爲基礎,疊加旋轉效果,因此繪製分針以前須要還原到初始座標系,我用的是 save
、 restore
函數保存狀態/還原狀態。
分針、秒針計算繪製過程雷同再也不贅述。
實現動畫過程只須要擦除已繪製的內容,再次繪製並重置變換效果便可,重繪使用 setInterval
、 requestAnimationFrame
均可以:
function draw() { ctx.resetTransform(); // 從新繪圖前清除變換效果 ctx.clearRect(0, 0, 400, 400); // 清除內容 ... window.requestAnimationFrame(draw); } window.requestAnimationFrame(draw);
詳見jsfiddle
該文章首發於個人博客https://blog.bingqichen.me/