HTML5 Canvas API 简要介绍

· · 科技·工程

本文采用 CC BY-NC-SA 4.0 授权。

Canvas API 简介

Canvas API 提供了一个通过 JavaScript 和 HTML 的 <canvas> 元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

如何使用 Canvas API?

Step 1

首先,你需要一个标准的页面:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>HTML Canvas API</title>
        <style>
            /*
              这里会有样式……
             */
        </style>
    </head>
    <body>
        <div id="content">
            <canvas id="cvs" width="1200" height="600"></canvas>
        </div>
        <script>
            // 编程区
        </script>
    </body>
</html>

打开效果:什么也没有。

Step 2

这里,有些同学就会问了:“为什么画面一片空白?”

<canvas> 确实存在,但没有内容。为了给它加上边框,请在 <style> 元素里添加以下样式:

#cvs {
   margin: 1em;
   border: 1px solid black;
}
body {
    width: 100%;
    height: 100%;
}

效果:

Step 3

接下来,就让我们开始画图吧!

请在 <script> 元素里添加以下代码:

const c = document.getElementById("cvs");
const ctx = c.getContext("2d");

// 画一个红色的正方形:
ctx.fillStyle = "red";
ctx.fillRect(10,10,100,100); // (x,y,w,h)

效果:

Step 4

如何在 <canvas> 上写字?使用 ctx.fillText(text: String,x: Number,y: Number, maxWidth: Number?)

添加以下代码:

// 写上“I love Luogu!”
ctx.fillStyle = "black";
ctx.font = "50px \"Times New Roman\",serif";
ctx.fillText("I love Luogu!", 200, 200);

效果:

Step 5

直接放图片怎么办?用 ctx.drawImage(image, dx, dy)

添加代码:

// 画一个洛谷 logo
const img = new Image();
img.src = "https://fecdn.luogu.com.cn/luogu/logo.png";
img.addEventListener("load", ev => {
    ctx.drawImage(img, 10, 200);
});

效果:

Step 6

状态的保存和恢复

使用这两个方法:

保存的状态存储在栈里,每当 save() 方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled

这段代码会画出一个钟(其中有一些没有介绍的方法,具体参见注释):

const canvas = document.getElementById("cvs");
const ctx = canvas.getContext("2d");

ctx.translate(20,20)
function clock() {
  const now = new Date();
  ctx.save();
  ctx.clearRect(0, 0, 150, 150);
  ctx.translate(75, 75); // 平移变换
  ctx.scale(0.4, 0.4); // 放大或缩小指定的倍数
  ctx.rotate(-Math.PI / 2); // 旋转,此处为 -90°,调用单位为 radian。
  ctx.strokeStyle = "black"; // 设置描边为黑色
  ctx.fillStyle = "white";
  ctx.lineWidth = 8;
  ctx.lineCap = "round";

  // 小时刻度
  ctx.save();
  for (let i = 0; i < 12; i++) {
    ctx.beginPath(); // 开始路径
    ctx.rotate(Math.PI / 6); // 30°
    ctx.moveTo(100, 0); // 移动“笔”
    ctx.lineTo(120, 0); // 用“笔”画线
    ctx.stroke(); // 描边
  }
  ctx.restore();

  // 分钟刻度
  ctx.save();
  ctx.lineWidth = 5;
  for (let i = 0; i < 60; i++) {
    if (i % 5 !== 0) {
      ctx.beginPath();
      ctx.moveTo(117, 0);
      ctx.lineTo(120, 0);
      ctx.stroke();
    }
    ctx.rotate(Math.PI / 30);
  }
  ctx.restore();

  const sec = now.getSeconds() + now.getMilliseconds() / 1000;
  const min = now.getMinutes();
  const hr = now.getHours() % 12;

  ctx.fillStyle = "black";

  // 显示图像描述
  canvas.innerText = `当前时间:${hr}:${min}`;

  // 时针
  ctx.save();
  ctx.rotate(
    (Math.PI / 6) * hr + (Math.PI / 360) * min + (Math.PI / 21600) * sec,
  );
  ctx.lineWidth = 14;
  ctx.beginPath();
  ctx.moveTo(-20, 0);
  ctx.lineTo(80, 0);
  ctx.stroke();
  ctx.restore();

  // 分针
  ctx.save();
  ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec);
  ctx.lineWidth = 10;
  ctx.beginPath();
  ctx.moveTo(-28, 0);
  ctx.lineTo(112, 0);
  ctx.stroke();
  ctx.restore();

  // 秒针
  ctx.save();
  ctx.rotate((sec * Math.PI) / 30);
  ctx.strokeStyle = "#D40000";
  ctx.fillStyle = "#D40000";
  ctx.lineWidth = 6;
  ctx.beginPath();
  ctx.moveTo(-30, 0);
  ctx.lineTo(83, 0);
  ctx.stroke();
  ctx.beginPath();
  ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
  ctx.fill();
  ctx.beginPath();
  ctx.arc(95, 0, 10, 0, Math.PI * 2, true);
  ctx.stroke();
  ctx.fillStyle = "rgb(0 0 0 / 0%)";
  ctx.arc(0, 0, 3, 0, Math.PI * 2, true); // 画圆
  ctx.fill(); // 填充路径
  ctx.restore();

  ctx.beginPath();
  ctx.lineWidth = 14;
  ctx.strokeStyle = "#325FA2";
  ctx.arc(0, 0, 142, 0, Math.PI * 2, true);
  ctx.stroke();

  ctx.restore();

  window.requestAnimationFrame(clock); // 在下一帧继续
}

window.requestAnimationFrame(clock);

效果(为了方便,删去了之前的代码):

导出

使用 canvas.toDataURL("image/jpeg") 来导出。

总而言之,这 canvas 就像一块画布,你可以自由的在上面作画。

Ref.