日中杂记——canvas

发布于:2022-11-09 ⋅ 阅读:(12) ⋅ 点赞:(0) ⋅ 评论:(0)

在线查看canvas绘制时钟

在线查看canvas绘制函数曲线

在线查看canvas绘制路径箭头

<canvas id="canvas" width="600" height="600"></canvas>

宽高只能通过canvas对象的属性来设置,不能通过css来设置

获取画笔

canvas.getContext(‘2d’)

getContext()方法:获取渲染上下文和绘画功能。2d图像,就传入参数’2d’

let canvas: any = document.getElementById('canvas');
let pen = canvas.getContext('2d');

moveTo()

起点

pen.moveTo(100, 100);

lineTo()

轨迹

上一次的终点是下一次轨迹的起点

pen.lineTo(200, 100);
pen.lineTo(200, 200);

stroke()

沿着轨迹绘制

pen.stroke();

closePath()

从终点连接到起点

pen.closePath();

fill()

闭合路径并填充颜色

pen.fill()

fillRect()

绘制一个填充矩形

fillRect(x, y, width, height)

strokeRect()

绘制一个矩形边框

pen.strokeRect(x, y, width, height);

arc()

绘制一段圆弧路径

pen.arc(x, y, radius, startAngle, endAngle, anticlockwise)

弧度值 = 角度值 * Math.PI / 180

  • x:圆弧中心(圆心)的 x 轴坐标。
  • y:圆弧中心(圆心)的 y 轴坐标。
  • radius:圆弧的半径。
  • startAngle:圆弧的起始角度(弧度值),x 轴方向开始计算。
  • endAngle:圆弧的结束角度(弧度值)。
  • anticlockwise:(可选)定义绘制的方向。
    • true:逆时针绘制圆弧
    • false:顺时针绘制圆弧(默认)

drawImage()

绘制图像

在这里插入图片描述

pen.drawImage(image, dx, dy)
pen.drawImage(image, dx, dy, dWidth, dHeight)
pen.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
  • image:绘制图片的dom对象。

  • dx

    image 的左上角在目标画布上 X 轴坐标。

  • dy

    image 的左上角在目标画布上 Y 轴坐标。

  • dWidth

    image 在目标画布上绘制的宽度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 宽度不会缩放。

  • dHeight

    image 在目标画布上绘制的高度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 高度不会缩放。

  • sx:(可选)
    需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的左上角 X 轴坐标。

  • sy:(可选)
    需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的左上角 Y 轴坐标。

  • sWidth:(可选)
    需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的宽度。如果不说明,整个矩形(裁剪)从坐标的 sx 和 sy 开始,到 image 的右下角结束。

  • sHeight:(可选)
    需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。使用负值将翻转这个图像。

  let img = new Image();
  img.src = require('./imgs/clock-bg.jpg');
  img.onload = function () {
    // 绘制图片
    pen.save();
    pen.drawImage(img, 0, 50);
    pen.restore();
  }

clip()

将当前创建的路径设置为当前剪切路径的方法。

浏览器会将所有的绘图操作都限制在本区域内执行。在默认情况下,剪辑区域的大小与Canvas画布大小一致。

  let img = new Image();
  img.src = require('./imgs/clock-bg.jpg');
  img.onload = function () {
    pen.save();
    pen.beginPath();
      
    pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
    // pen.fill();
    // 将上面的区域作为剪辑区域
    pen.clip();
      
    pen.drawImage(img, 0, 50);
    pen.restore();
  }

clearRect()

清除指定矩形区域,让清除部分完全透明。

pen.clearRect(x, y, width, height)

beginPath()

开始一个新的路径

pen.beginPath()

save()

保存画布状态

pen.save()

restore()

恢复save()保存的状态

pen.restore()

变换

  • rotate(angle):旋转
    • angle顺时针旋转的弧度。弧度值 = 角度值 * Math.PI / 180
    • 旋转中心点是(0,0),可以通过translate()转换来改变中心点
  • scale():放大缩小
  • translate():平移

线型

  • lineWidth:线的宽度。默认1
  • lineCap:线末端的类型。允许的值: butt (默认), round, square.
  • lineJoin:两线相交拐点的类型。允许的值:round, bevel, miter(默认)。

文本

  • fillText(text, x, y, [maxWidth])

    • text:使用当前的 font, textAlign, textBaseline 和 direction 值对文本进行渲染。

    • x:文本起点的 x 轴坐标。

    • y:文本起点的 y 轴坐标。

    • maxWidth(可选):绘制的最大宽度。超出宽度会缩放字体。

    pen.font = "48px serif";
    pen.fillText("Hello world", 50, 100);
    
  • strokeText():在 (x,y) 位置绘制(描边)文本。

样式

  • font:当前字体样式的属性。

    pen.font = "10px sans-serif"; // 默认值
    
  • textAlign:文本对齐设置。允许的值: start (默认), end, left, right 或 center.

  • direction:文本的方向。允许的值: ltr, rtl, inherit (默认).

  • fillStyle:图形内部的颜色和样式。默认 #000 (黑色).

  • strokeStyle:图形边线的颜色和样式。默认 #000 (黑色).

阴影

  • shadowColor:阴影的颜色。默认 fully-transparent black.
  • shadowBlur:模糊效果。默认 0
  • shadowOffsetX:阴影水平方向的偏移量。默认 0.
  • shadowOffsetY:阴影垂直方向的偏移量。默认 0.
  pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
  pen.shadowBlur = 20;
  pen.shadowOffsetX = -12;
  pen.shadowOffsetY = 20;

用例

绘制线段

  pen.moveTo(100, 100);
  pen.lineTo(200, 100);
  pen.lineTo(200, 200);
  pen.stroke();

绘制网格

  // 绘制网格
  const step = 100;
  const h = 600;
  const w = 300;
  const w_l = w / step;
  const h_l = h / step;

  // 横着的线
  for (let i = 0; i <= h_l; i++) {
    pen.beginPath();
    pen.moveTo(0, i * step);
    pen.lineTo(w, i * step);
    pen.stroke();
  }
  // 竖着的线
  for (let i = 0; i <= w_l; i++) {
    pen.beginPath();
    pen.moveTo(i * step, 0);
    pen.lineTo(i * step, h);
    pen.stroke();
  }

改变旋转中心

/**
 * canvas旋转中心偏移值
 * @param width canvas宽
 * @param center 中心坐标点 center:{x:100,y:100}
 * @param arc 旋转的弧度值
 */
function rotateOriginOffset(
  width: number,
  center: { x: number; y: number },
  arc: any
) {
  const r1 = width - center.x;
  const xRes1 = Math.cos(arc) * r1;
  const yRes1 = Math.sin(arc) * r1;
  const x1 = center.x + xRes1;
  const y1 = center.y + yRes1;

  const x0 = width;
  const y0 = center.y;
  const c0 = Math.sqrt(Math.pow(x0, 2) + Math.pow(y0, 2));
  const arc0 = Math.atan2(y0, x0);
  const arc_0 = arc0 + arc;
  const y2 = Math.sin(arc_0) * c0;
  const x2 = y2 / Math.tan(arc_0);

  const xLength = x1 - x2;
  const yLength = y1 - y2;
  return {
    x: xLength,
    y: yLength,
  };
}
  const offset = rotateOriginOffset(width, center, arc);
  pen.translate(offset.x, offset.y); // 改变旋转中心
  pen.rotate(arc);

绘制时钟

CodePen在线查看

这个用例基本把上面的方法都用了一遍。
使用到的框架是QuasarCLI
vue3+quasar2.0+ts

在这里插入图片描述

Template
<template>
  <div class="fit">
    <div>canvas</div>

    <div>
      <q-btn label="显示网格" color="primary" @click="showGrid" />
    </div>
    <div class="relative-position">
      <canvas
        id="canvas"
        width="600"
        height="600"
        style="border: 1px solid red; position: absolute; top: 0; left: 0"
      ></canvas>
      <canvas
        id="canvas-seconds"
        width="600"
        height="600"
        style="
          border: 1px solid transparent;
          position: absolute;
          top: 0;
          left: 0;
        "
      ></canvas>
      <canvas
        id="canvas-grid"
        width="600"
        height="600"
        style="
          border: 1px solid transparent;
          position: absolute;
          top: 0;
          left: 0;
        "
      ></canvas>
    </div>
  </div>
</template>
Script
<script setup lang="ts">
import { onMounted, ref, reactive, onBeforeUnmount } from 'vue';

const isShowGrid = ref(false);
const state = reactive({
  center: {
    x: 300,
    y: 300,
  }, // 圆心
  r: 200, // 半径,
  timer: null as any,
  canvasSeconds: null as any,
});

onBeforeUnmount(() => {
  if (state.timer) {
    clearInterval(state.timer);
    state.timer = null;
  }
});

onMounted(() => {
  state.canvasSeconds = document.getElementById('canvas-seconds');
  let canvas: any = document.getElementById('canvas');
  let pen = canvas.getContext('2d');
  const { center, r } = state;

  let img = new Image();
  img.src = require('./imgs/clock-bg.jpg');
  img.onload = function () {
    // 背景色
    pen.save();
    pen.fillStyle = '#2d2d2d';
    pen.fillRect(0, 0, 600, 600);
    pen.restore();

    // 绘制图片
    pen.save();
    pen.beginPath();
    pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
    // pen.fill();
    // 将上面的区域作为剪辑区域
    pen.clip();
    pen.drawImage(img, 241, 68, 300, 300, 100, 100, 2 * r, 2 * r);
    // pen.drawImage(img, -90, 80);
    pen.restore();

    pen.save();
    // 大刻度
    for (let i = 0; i < 12; i++) {
      pen.beginPath();
      const a1 = Math.sin(toArc(i * 30)) * (r - 10); // (r - 10) 10是刻度与圆弧边线的间隙
      const b1 = Math.cos(toArc(i * 30)) * (r - 10);
      const x0 = center.x + a1;
      const y0 = center.y - b1;
      const a2 = Math.sin(toArc(i * 30)) * 8; // 8是大刻度线的长度
      const b2 = Math.cos(toArc(i * 30)) * 8;
      const x1 = x0 - a2;
      const y1 = y0 + b2;
      pen.moveTo(x0, y0);
      pen.lineTo(x1, y1);
      pen.lineWidth = 4;
      pen.lineCap = 'round';
      pen.strokeStyle = '#5CD8F9';

      // pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
      // pen.shadowBlur = 10;
      // pen.shadowOffsetX = -8;
      // pen.shadowOffsetY = 10;

      pen.stroke();
    }
    pen.restore();

    // 小刻度
    pen.save();
    for (let i = 0; i < 60; i++) {
      pen.beginPath();
      const a1 = Math.sin(toArc(i * 6)) * (r - 10); // (r - 10) 10是刻度与圆弧边线的间隙
      const b1 = Math.cos(toArc(i * 6)) * (r - 10);
      const x0 = center.x + a1;
      const y0 = center.y - b1;
      const a2 = Math.sin(toArc(i * 6)) * 4; // 4是大刻度线的长度
      const b2 = Math.cos(toArc(i * 6)) * 4;
      const x1 = x0 - a2;
      const y1 = y0 + b2;
      pen.moveTo(x0, y0);
      pen.lineTo(x1, y1);
      pen.lineWidth = 1;
      pen.lineCap = 'round';
      pen.strokeStyle = '#48E6FE';

      pen.stroke();
    }
    pen.restore();

    // 绘制文本
    pen.save();
    for (let i = 0; i < 12; i++) {
      pen.beginPath();
      const a1 = Math.sin(toArc(i * 30)) * (r - 40);
      const b1 = Math.cos(toArc(i * 30)) * (r - 40);

      const num = i || 12;
      const x0 = center.x + a1;
      const y0 = center.y - b1;

      pen.font = '26px sans-serif';
      pen.textAlign = 'center';
      pen.textBaseline = 'middle';

      pen.fillStyle = '#5CD8F9';
      pen.fillText(`${num}`, x0, y0);
    }
    pen.restore();

    // 外圆
    pen.save();
    pen.beginPath();
    pen.arc(center.x, center.y, r, 0, 2 * Math.PI);
    pen.lineWidth = 10;
    pen.strokeStyle = '#0E4A6F';

    // 阴影
    pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
    pen.shadowBlur = 20;
    pen.shadowOffsetX = -12;
    pen.shadowOffsetY = 20;
    pen.stroke();
    pen.restore();

    // 圆心
    pen.save();
    pen.beginPath();
    pen.arc(center.x, center.y, 16, 0, 2 * Math.PI);
    pen.fillStyle = '#204969';
    pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
    pen.shadowBlur = 5;
    pen.shadowOffsetX = -10;
    pen.shadowOffsetY = 10;
    pen.fill();
    pen.restore();

    state.timer = setInterval(() => {
      drawSeconds();
    }, 1000);
  };
});

function drawSeconds() {
  let pen = state.canvasSeconds.getContext('2d');

  const { center, r } = state;

  const date = getDate();
  // console.log('---', date);

  const secondsDeg = date.seconds * 6 + 270;
  const minDeg = date.minutes * 6 + 270 + date.seconds * 0.1;
  const hoursDeg =
    date.hours * 30 + 270 + date.minutes * 0.5 + date.seconds * (1 / 120);
  pen.clearRect(0, 0, 600, 600);

  // 时间显示
  pen.save();
  pen.font = '26px sans-serif';
  pen.textAlign = 'center';
  pen.textBaseline = 'middle';
  pen.fillStyle = '#5CD8F9';
  const hStr = date.hours < 10 ? `0${date.hours}` : date.hours;
  const mStr = date.minutes < 10 ? `0${date.minutes}` : date.minutes;
  const sStr = date.seconds < 10 ? `0${date.seconds}` : date.seconds;

  const timeStr = `${hStr}:${mStr}:${sStr}`;
  pen.fillText(timeStr, center.x, center.y - (2 / 5) * r);
  pen.restore();

  // 时针
  pen.save();
  const offseHour = rotateOriginOffset(600, center, toArc(hoursDeg));
  pen.translate(offseHour.x, offseHour.y); // 改变旋转中心
  pen.rotate(toArc(hoursDeg));
  pen.beginPath();
  pen.moveTo(center.x - 20, center.y);
  pen.lineTo(center.x + r - 110, center.y);
  pen.lineWidth = 9;
  pen.lineCap = 'round';
  pen.strokeStyle = '#1E88D2';

  pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
  pen.shadowBlur = 5;
  pen.shadowOffsetX = -10;
  pen.shadowOffsetY = 10;
  pen.stroke();
  pen.restore();

  pen.save();
  pen.beginPath();
  pen.arc(center.x, center.y, 12, 0, 2 * Math.PI);
  pen.fillStyle = '#71C6F7';
  pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
  pen.shadowBlur = 2;
  pen.shadowOffsetX = -2;
  pen.shadowOffsetY = 2;
  pen.fill();
  pen.restore();

  // 分针
  pen.save();
  const offsetMin = rotateOriginOffset(600, center, toArc(minDeg));
  pen.translate(offsetMin.x, offsetMin.y); // 改变旋转中心
  pen.rotate(toArc(minDeg));
  pen.beginPath();
  pen.moveTo(center.x - 20, center.y);
  pen.lineTo(center.x + r - 80, center.y);
  pen.lineWidth = 5;
  pen.lineCap = 'round';
  pen.strokeStyle = '#71C6F7';

  pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
  pen.shadowBlur = 5;
  pen.shadowOffsetX = -10;
  pen.shadowOffsetY = 10;

  pen.stroke();
  pen.restore();

  // 秒针
  pen.save();
  const offset = rotateOriginOffset(600, center, toArc(secondsDeg));
  pen.translate(offset.x, offset.y); // 改变旋转中心
  pen.rotate(toArc(secondsDeg));
  pen.beginPath();
  pen.moveTo(center.x - 40, center.y);
  pen.lineTo(center.x + r - 10, center.y);
  pen.lineWidth = 2;
  pen.lineCap = 'round';
  pen.strokeStyle = '#71C6F7';

  pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
  pen.shadowBlur = 5;
  pen.shadowOffsetX = -10;
  pen.shadowOffsetY = 10;

  pen.stroke();
  pen.restore();

  pen.save();
  pen.beginPath();
  pen.arc(center.x, center.y, 5, 0, 2 * Math.PI);
  pen.fillStyle = '#204969';
  pen.shadowColor = 'rgba(0, 0, 0, 0.35)';
  pen.shadowBlur = 2;
  pen.shadowOffsetX = -2;
  pen.shadowOffsetY = 2;
  pen.fill();
  pen.restore();
}

// 绘制网格
function drawGrid(color: string, w: number, h: number, pen: any) {
  const step = 100;
  const w_l = w / step;
  const h_l = h / step;

  pen.save();
  // 横着的线
  for (let i = 0; i <= h_l; i++) {
    pen.beginPath();
    pen.strokeStyle = color;
    pen.moveTo(0, i * step);
    pen.lineTo(w, i * step);
    pen.stroke();
  }
  // 竖着的线
  for (let i = 0; i <= w_l; i++) {
    pen.beginPath();
    pen.moveTo(i * step, 0);
    pen.lineTo(i * step, h);
    pen.stroke();
  }
  pen.restore();
}

function showGrid() {
  let canvas: any = document.getElementById('canvas-grid');
  let pen = canvas.getContext('2d');
  isShowGrid.value = !isShowGrid.value;
  if (isShowGrid.value) {
    drawGrid('#FD7013', 600, 600, pen); // 网格
  } else {
    pen.clearRect(0, 0, 600, 600);
  }
}

function toArc(degree: number) {
  return (degree * Math.PI) / 180;
}

function rotateOriginOffset(
  width: number,
  center: { x: number; y: number },
  arc: any
) {
  const r1 = width - center.x;
  const xRes1 = Math.cos(arc) * r1;
  const yRes1 = Math.sin(arc) * r1;
  const x1 = center.x + xRes1;
  const y1 = center.y + yRes1;

  const x0 = width;
  const y0 = center.y;
  const c0 = Math.sqrt(Math.pow(x0, 2) + Math.pow(y0, 2));
  const arc0 = Math.atan2(y0, x0);
  const arc_0 = arc0 + arc;
  const y2 = Math.sin(arc_0) * c0;
  const x2 = y2 / Math.tan(arc_0);

  const xLength = x1 - x2;
  const yLength = y1 - y2;
  return {
    x: xLength,
    y: yLength,
  };
}

function getDate() {
  const myDate = new Date();
  return {
    hours: myDate.getHours(),
    minutes: myDate.getMinutes(),
    seconds: myDate.getSeconds(),
  };
}
</script>