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()保存当前状态(颜色、变换、字体)restore()恢复当前状态
保存的状态存储在栈里,每当 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.
- MDN
- W3C Canvas 规范