使用canvas模拟IOS时钟

187 阅读2分钟

绘制背景

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>模拟IOS时钟</title>
  <style>
   body {
      margin: 0;
      padding: 0;
      background-color: #ddd;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .clock {
      width: 300px;
      height: 300px;
      background-color: black;
      border-radius: 50px;
    }
  </style>
</head>
<body>
  <div class="clock">
    <canvas class="canvas" width="300" height="300"></canvas>
  </div>

  <script>
    const canvasEl = document.querySelector('canvas')

    const ctx = canvasEl.getContext('2d')

    // 绘制 时钟白色背景
    function drawBg() {
      ctx.save()

      ctx.translate(150, 150)
      ctx.fillStyle="#fff"

      ctx.beginPath()
      ctx.arc(0, 0, 130, 0, Math.PI * 2)
      ctx.fill()
      ctx.closePath()

      ctx.restore()
    }


    function draw() {
      ctx.clearRect(0, 0, 300, 300)

      drawBg()
      
      requestAnimationFrame(draw)
    }

    draw()
  </script>
</body>
</html>

绘制刻度

先绘制一个刻度3

function drawNumber() {
  ctx.save()

  ctx.translate(150, 150)
  ctx.font="30px fangsong";
  ctx.fillText(3, 100, 0)

  ctx.restore()
}

在canvas中绘制文本需要使用fillText,而fillText需要传递三个参数

ctx.fillText(<font>, <X>, <Y>)

所以我们需要计算出每一个刻度所对应的X和Y轴坐标

image.png

我们知道三角形 三条边的关系如下

此时A是圆点,AC是圆的半径

x= AB = cos(a) * AC => x = Math.cos(a对应的弧度) * R

y= BC = sin(a) * AC => y = Math.sin(a对应的弧度) * R

function drawNumber() {
  ctx.save()

  const numbers = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2]

  ctx.translate(150, 150)
  ctx.font="30px fangsong";

  // 绘制文本的时候,确保绘制点 位于 绘制文字的中心点
  // 避免因为绘制文字的中心不对 而导致时钟刻度盘不居中
  ctx.textBaseline="middle"
  ctx.textAlign="center"

  // 一圈是 Math.PI * 2
  // 需要绘制12个刻度,所以每一份刻度所占的弧度为  Math.PI * 2 / 12
  // 所以对于每一个刻度 对于的弧度极为  Math.PI * 2 / 12 * i
  for(let i =0; i < numbers.length; i++) {
    ctx.fillText(numbers[i],
      Math.cos(Math.PI / 6 * i) * 100,
      Math.sin(Math.PI / 6 * i) * 100
    )
  }

  ctx.restore()
}

绘制时分针

function drawHands() {
  ctx.save()

  const date = new Date()
  const hours = date.getHours()
  const minutes = date.getMinutes()
  const seconds = date.getSeconds()

  ctx.translate(150, 150)

  // 绘制时钟,对于小时而言
  // 1小时 -> Math.PI * 2 / 12 === 12个刻度
  // 1分钟 -> Math.PI * 2 / 12 / 60
  // 1秒 -> Math.PI * 2 / 12 / 60 / 60
  ctx.save()
  ctx.beginPath()
  ctx.lineWidth = 5
  ctx.lineCap = 'round'
  ctx.rotate(Math.PI * 2 / 12 * hours + Math.PI * 2 / 12 / 60 * minutes + Math.PI * 2 / 12 / 60 / 60 * seconds)
  ctx.moveTo(0, 0)
  ctx.lineTo(0, -50)
  ctx.stroke()
  ctx.closePath()
  ctx.restore()

  // 绘制分钟
  // 一圈是60分钟
  // 1分钟 Math.PI * 2 / 60
  // 1秒 Math.PI * 2 /  60 / 60
  ctx.save()
  ctx.beginPath()
  ctx.lineWidth = 3
  ctx.lineCap = 'round'
  ctx.rotate(Math.PI * 2 / 60 * minutes + Math.PI * 2 / 60 / 60 * seconds)
  ctx.moveTo(0, 0)
  ctx.lineTo(0, -65)
  ctx.stroke()
  ctx.closePath()
  ctx.restore()

  // 绘制秒针
  // 一圈是60秒
  // 1s Math.PI * 2 / 60
  ctx.save()
  ctx.beginPath()
  ctx.moveTo(0, 0)
  ctx.rotate(Math.PI * 2 / 60 * seconds)
  ctx.strokeStyle="#f00"
  ctx.lineCap = 'round'
  ctx.lineTo(0, -85)
  ctx.stroke()
  ctx.closePath()
  ctx.restore()

  ctx.restore()
}

绘制圆盘

function drawCricle() {
  ctx.save()

  ctx.translate(150, 150)

  ctx.beginPath()
  ctx.moveTo(0, 0)
  ctx.arc(0, 0, 8, 0, Math.PI * 2)
  ctx.fill()
  ctx.closePath()

  ctx.beginPath()
  ctx.moveTo(0, 0)
  ctx.arc(0, 0, 5, 0, Math.PI * 2)
  ctx.fillStyle = "gray"
  ctx.fill()
  ctx.closePath()

  ctx.restore()
}

绘制刻度

绘制小时刻度

function drawHourScale() {
  ctx.save()

  ctx.translate(150, 150)

  for(let i = 0; i < 12; i++) {
    ctx.rotate(Math.PI * 2 / 12)
    ctx.beginPath()
    ctx.lineWidth = 3
    ctx.moveTo(0, -130)
    ctx.lineTo(0, -122)
    ctx.stroke()
    ctx.closePath()
  }

  ctx.restore()
}

绘制分钟刻度

function drawMinuteScale() {
  ctx.save()

  ctx.translate(150, 150)

  for(let i = 0; i < 60; i++) {
    ctx.rotate(Math.PI * 2 / 60)
    ctx.beginPath()
    ctx.moveTo(0, -130)
    ctx.lineTo(0, -125)
    ctx.stroke()
    ctx.closePath()
  }

  ctx.restore()
}

最终效果

使用canvas模拟IOS时钟