canvas实现时钟、写字板功能

149 阅读3分钟

记录一波n年前通过canvas实现的一些小demo

1.实现一个时钟

效果如下:

1.1 先实现一个基本的表盘

1.1.1 绘制表盘上的时刻数字
<!DOCTYPE html>
<html>
  
  <head>
    <meta charset="utf-8">
    <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="move.js"></script>
    <style type="text/css" media="all">
      div {
        text-align: center;
        margin-top: 250px;
        position: absolute;
        
      }
      
      #clock {
        border: 0px solid #ccc;
      }
    </style>
  </head>
  
  <body>
    <div id='box'>
      <canvas id="clock" height="200px" width="200px"></canvas>
    </div>
    
  </body>
   <script>
    const dom = document.getElementById('clock');
    const ctx = dom.getContext('2d');
    const width = ctx.canvas.width;
    const height = ctx.canvas.height;
    const r = width / 2;

    // 绘制表盘
    const drawBackGround = () => {
      ctx.save();
      ctx.translate(r, r);// translate() 方法重新映射画布上的 (0,0) 位置。
      ctx.beginPath();
      ctx.lineWidth = 6;

      ctx.arc(0, 0, r - 3, 0, 2 * Math.PI);//先绘制一个圆圈
      // 半径为r-lineWidth的一半
      ctx.stroke();
      ctx.font = '20px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      const hourNum = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2];
      
      //绘制每一个时刻对应的数字
      hourNum.forEach(function (number, i) {
        const rad = 2 * Math.PI / 12 * i;// 对应的弧度
        const x = Math.cos(rad) * (r - 20);
        const y = Math.sin(rad) * (r - 20);
        ctx.fillText(number, x, y);
      });
    }
    drawBackGround()
 </script>
</html>

得到效果如下:

1.1.2 绘制表盘上的分对应小点
// 绘制表盘
const drawBackGround = () => {
  ctx.save();
  ctx.translate(r, r);// translate() 方法重新映射画布上的 (0,0) 位置。
  ctx.beginPath();
  ctx.lineWidth = 6;
  
  //
  ctx.arc(0, 0, r - 3, 0, 2 * Math.PI);
  // 半径为r-lineWidth的一半
  ctx.stroke();
  ctx.font = '20px Arial';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  const hourNum = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2];
  hourNum.forEach(function (number, i) {
    const rad = 2 * Math.PI / 12 * i;// 对应的弧度
    const x = Math.cos(rad) * (r - 20);
    const y = Math.sin(rad) * (r - 20);
    ctx.fillText(number, x, y);
  });
  for (i = 0; i < 60; i++) {
    const rad = 2 * Math.PI / 60 * i;
    const x = Math.cos(rad) * (r - 10);
    const y = Math.sin(rad) * (r - 10);
    ctx.beginPath();
    
    ctx.fillStyle = 'gray';
    ctx.arc(x, y, 1, 0, 2 * Math.PI);
    
    ctx.fill();
  }
  
    }

得到效果如下:

但是观察发现,整5分的时候圆点会比较突出,改写代码如下
 // 绘制表盘
    const drawBackGround = () => {
      ctx.save();
      ctx.translate(r, r);// translate() 方法重新映射画布上的 (0,0) 位置。
      ctx.beginPath();
      ctx.lineWidth = 6;

      //
      ctx.arc(0, 0, r - 3, 0, 2 * Math.PI);
      // 半径为r-lineWidth的一半
      ctx.stroke();
      ctx.font = '20px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      const hourNum = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2];
      hourNum.forEach(function (number, i) {
        const rad = 2 * Math.PI / 12 * i;// 对应的弧度
        const x = Math.cos(rad) * (r - 20);
        const y = Math.sin(rad) * (r - 20);
        ctx.fillText(number, x, y);
      });
      for (i = 0; i < 60; i++) {
        const rad = 2 * Math.PI / 60 * i;
        const x = Math.cos(rad) * (r - 10);
        const y = Math.sin(rad) * (r - 10);
        ctx.beginPath();

        if (i % 5 === 0) {

          ctx.fillStyle = 'black';
          ctx.arc(x, y, 2, 0, 2 * Math.PI);

        }
        else {
          ctx.fillStyle = 'gray';
          ctx.arc(x, y, 1, 0, 2 * Math.PI);

        }
        ctx.fill();
      }

    }

此时表盘比较像那么回事了

1.2 实现时针、分针、秒针

1.2.1 实现时针

因为分钟会影响小时的旋转度数,所以需要通过小时+分钟才能确定时针的位置

 const drawHour = (hour, Minutes) => {
      ctx.save();// 因为旋转会导致下一次还是处于旋转状态,所以保存当前状态然后在清楚状态
      ctx.beginPath();
      const rad = 2 * Math.PI / 12 * hour;
      const mrad = 2 * Math.PI / 12 / 60 * Minutes;
      ctx.rotate(rad + mrad);
      ctx.lineCap = "round";
      ctx.lineWidth = 5;
      ctx.moveTo(0, 10);
      ctx.lineTo(0, -r / 2);
      ctx.stroke();
      ctx.restore();
    }
1.2.2 实现分针
const  drawMinutes = (Minutes) => {
      ctx.save();
      ctx.beginPath();
      const rad = 2 * Math.PI / 60 * Minutes;// 乘值不同小时12分钟60
      ctx.rotate(rad);
      ctx.lineWidth = 3;

      ctx.moveTo(0, 10);
      ctx.lineTo(0, -r + 30);
      ctx.stroke();
      ctx.restore();
    }
1.2.3 实现秒针
 const  drawSeconds = (seconds) => {
      ctx.save();
      ctx.beginPath();
      ctx.fillStyle = 'red';
      const rad = 2 * Math.PI / 60 * seconds;// 乘值不同小时12分钟60
      ctx.rotate(rad);
      ctx.moveTo(-2, 20);
      ctx.lineTo(2, 20);
      ctx.lineTo(1, -r + 18);
      ctx.lineTo(-1, -r + 18)
      ctx.fill();
      ctx.restore();
    }

通过draw方法获取时间后绘制时针分针以及秒针

const draw = () => {
      drawBackGround();
      const date = new Date();
      const h = date.getHours();
      const m = date.getMinutes();
      const s = date.getSeconds();
      drawHour(h, m);
      drawMinutes(m);
      drawSeconds(s);
    }
    draw();

得到效果如下:

但是一般情况,中心会有一个白色圆点,添加drawArc方法:

 const drawArc = () => {
      ctx.beginPath();
      ctx.fillStyle = 'white';
      ctx.arc(0, 0, 5, 0, 2 * Math.PI);
      ctx.fill();
    }

此时的效果如下:

此时的时钟已经绘制完成,但是还不会动

1.3 让时钟跑起来

setInterval(draw, 1000);

其实只需要给时钟添加一个绘制的定时器就可以了,但是此时会存在一个问题:

需要恢复绘制的状态,所以需要在draw方法中添加ctx.restore(); 再次运行,发现还存在问题:

这是因为没有清楚之前绘制的状态,改写draw方法

  const draw = () => {
      ctx.clearRect(0, 0, width, height);//
      drawBackGround();

      const date = new Date();
      const h = date.getHours();
      const m = date.getMinutes();
      const s = date.getSeconds();
      drawHour(h, m);
      drawMinutes(m);
      drawSeconds(s);
      drawArc();
      ctx.restore();
    }
    draw();

再次运行,大功告成

2.实现简单得写字板功能

之前得业务场景,老师需要在线批阅学生的作业,并进行批注,所以使用canvans实现了此功能,简单得demo代码如下:

2.1 先实现一个简单得界面

<!DOCTYPE html>
<html>
  
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, height=device-height,initial-scale=1.0, 
                                   maximum-scale=1.0,minimum-scale=1.0, user-scalable=0">
    <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
    <style type="text/css">
      .operate {
        display: flex;
      }
      
      #canvas {
        display: block;
        border: 1px #ccc solid;
        margin: 20px;
      }
      
      input,
      #select,
      #button,
      #line {
        width: 10%;
        height: 40px;
        margin-left: 16px;
      }
    </style>
    
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <div class="operate">
      <input type="color" name="color" id="color">
      <input type="button" name="button" id="button" value="清空">
      
      <select id='select'>
        <option>1</option>
        <option>3</option>
        <option>5</option>
        <option>7</option>
        <option>9</option>
        <option>11</option>
        <option>13</option>
      </select>
      
      <select id='line'>
        <option value="solid">实线</option>
        <option value="dashed">虚线</option>
      </select>
    </div>
    <script>
      </body>
      
</html>

如下:

2.2 实现 写字板功能

通过记录鼠标再画板区域移动得位置,时时执行位置moveTo到lineTo得过程即可

const x = 375;
const y = 500;
let isMove = false;
let oldPosition = { x: 0, y: 0 };
const canvasDom = document.getElementById('canvas');
const ctx = canvasDom.getContext('2d');
ctx.canvas.width = x;
ctx.canvas.height = y;

// 初始化基本操作
const initOperate = () => {
  ctx.lineCap = 'round';
  
  // 1.清空功能
  $('#button').click(() => {
    ctx.clearRect(0, 0, x, y);
  });
  
  $('#select').change(
    () => {
      ctx.lineWidth = $('#select').val();
    });
  $('#color').change(
    () => {
      ctx.lineWidth = $('s#elect').val();
      const color = $('#color').val();
      ctx.strokeStyle = color;
    }
  );
  
  canvasDom.onmousedown = (e) => {
    e.preventDefault();
    isMove = true;
    oldPosition = getPosition(e.clientX, e.clientY);
  };
  canvasDom.onmousemove = (e) => {
    e.preventDefault();
    const optionValue = $('#line').val()
    drawLine(e,optionValue);
  }
  canvasDom.onmouseup = function (e) {
    e.preventDefault();
    isMove = false;
  };
  canvasDom.onmouseout = function (e) {
    e.preventDefault();
    isMove = false;
  };
  
}

// 获取最新得位置
const getPosition = (x, y) => {
  const json = canvasDom.getBoundingClientRect()
  return { x: x - json.left, y: y - json.top }
}

// 绘画功能
const drawLine=(e,type)=>{
  let newPosition = {}
  if (isMove) {
    newPosition = getPosition(e.clientX, e.clientY);
    type==='dashed'&&ctx.setLineDash([3, 10]);//虚线绘制
    ctx.beginPath();
    ctx.moveTo(oldPosition.x, oldPosition.y);
    ctx.lineTo(newPosition.x, newPosition.y);
    ctx.stroke();
    oldPosition = newPosition;
  }
}

    initOperate()

即可实现绘图的基本功能:

3.代码地址