canvas 高仿 Apple Watch 錶盤

不知道你們童年時候有沒有在手上畫手錶的經歷,剛好最近在看 canvas ,因而就誕生了這個高仿錶盤。 javascript

童年畫手錶

實現過程

我這裏參照了 Apple Watch 中的這個錶盤:css

Apple Watch 錶盤

繪製錶盤背景

<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) 爲起始點,以 cp1cp2 爲控制點,(-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);

值得注意的一點是:咱們在繪製完時針後,若是接下去直接計算角度繪製分針,上下文會以上次旋轉的角度爲基礎,疊加旋轉效果,因此繪製分針以前須要還原到初始座標系,我用的是 saverestore 函數保存狀態/還原狀態。

分針、秒針計算繪製過程雷同再也不贅述。

實現動畫

實現動畫過程只須要擦除已繪製的內容,再次繪製並重置變換效果便可,重繪使用 setIntervalrequestAnimationFrame 均可以:

function draw() {
    ctx.resetTransform(); // 從新繪圖前清除變換效果
    ctx.clearRect(0, 0, 400, 400); // 清除內容
    ...
    window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);

成果圖

完整實現

詳見jsfiddle

該文章首發於個人博客https://blog.bingqichen.me/

相關文章
相關標籤/搜索