JavaScript青少年简明教程:canvas(画布)元素入门
<canvas>元素通常被称为“画布”(Canvas),它使用JavaScript可以在网页上动态地绘制图形、图像、动画等内容。
<canvas> 元素是 HTML5 引入的一种绘图元素,允许开发者使用 JavaScript 动态地绘制图形、渲染文本、处理动画和图像。通过结合 <canvas> 和 JavaScript,可以创建复杂的图形和视觉效果,包括游戏、图表、数据可视化等。
canvas坐标体系
电脑上的坐标系和数学上的坐标系稍微有点不同,坐标的原点在绘制区域(这里是Canvas)的左上角,X轴正向朝右,Y轴正向朝下,如下图:
canvas默认情况下以canvas的左上角为坐标原点(0,0),沿x轴向右为正值,沿y轴向下为正值。大小是 300 像素宽 × 150 像素高(可以修改canvas的大小)。坐标的单位是像素(pixel缩写px)。
设置 Canvas 大小,有两种方式:
1)HTML属性方式:
<canvas id="myCanvas" width="500" height="300"></canvas>
2)JavaScript方式:
let canvas = document.getElementById('myCanvas');
canvas.width = 500;
canvas.height = 300;
<canvas> 元素本身不提供绘图功能,仅提供了一个用于绘图的区域,但可被用来通过 JavaScript(Canvas API 或 WebGL API)绘制图形及图形动画。https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/canvas
下面是一个简单的使用 <canvas> 元素的例子,直观感受之,源码如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas 示例</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="300" style="border:1px solid #000000;">
您的浏览器不支持Canvas
</canvas>
<script>
// 获取canvas元素
let canvas = document.getElementById('myCanvas');
// 获取2D渲染上下文
let ctx = canvas.getContext('2d');
// 使用Canvas API绘制一个矩形
ctx.fillStyle = '#FF0000'; // 设置填充颜色为红色
ctx.fillRect(10, 10, 100, 100); // 在(10, 10)的位置绘制一个100x100的矩形
</script>
</body>
</html>
例子中,我们创建了一个 400x300 像素的 <canvas> 元素,并使用 JavaScript 获取了它的 2D 渲染上下文(getContext('2d'))。然后,我们设置填充颜色为红色,并使用 fillRect 方法绘制了一个 100x100 像素的矩形。运行效果如下:
Canvas API 提供了许多其他的绘图方法和属性,如绘制圆形、线条、渐变、图像等。你可以通过 JavaScript 编程来创建复杂的动画和交互式的图形。
<canvas> 是HTML5 中新增的一项强大功能,它提供了一个用于绘图的区域。默认情况下,<canvas> 元素没有内容,需要通过 JavaScript 进行绘制。典型的 <canvas> 元素定义如下:
<canvas id="myCanvas" width="500" height="500"></canvas>
获取绘图上下文
要在 <canvas> 上进行绘图,首先需要获取其绘图上下文(context)。<canvas> 元素提供了多种上下文类型,但最常用的是 2D 上下文。
let canvas = document.getElementById('myCanvas');
let context = canvas.getContext('2d');
要在HTML5的<canvas>元素上进行2D绘图,我们需要先获取其2D渲染上下文。这是通过调用canvas元素的getContext('2d')方法来完成的。
什么是上下文?
在这里,“上下文”可以理解为一个绘图环境或者绘图API的集合。它提供了一系列用于在canvas上绘制图形、文本、图像等的方法和属性。例如:
fillRect(): 绘制填充矩形
strokeRect(): 绘制描边矩形
arc(): 绘制圆弧或圆
还可以通过上下文设置各种绘图属性,如fillStyle(填充颜色)、strokeStyle(描边颜色)、lineWidth(线宽)等。
为什么需要获取上下文?
canvas元素本身只是一个容器,它不提供任何绘图功能。要在canvas上绘图,我们需要使用特定的绘图API,这就是上下文所提供的。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas 2D Context Example</title>
</head>
<body>
<canvas id="myCanvas" width="300" height="200"></canvas>
<script>
// 获取canvas元素
const canvas = document.getElementById('myCanvas');
// 获取2D渲染上下文
const ctx = canvas.getContext('2d');
// 使用上下文进行绘图
ctx.fillStyle = 'blue'; // 设置填充颜色
ctx.fillRect(50, 50, 100, 75); // 绘制一个填充矩形
ctx.strokeStyle = 'red'; // 设置描边颜色
ctx.lineWidth = 5; // 设置线宽
ctx.strokeRect(75, 75, 150, 100); // 绘制一个描边矩形
ctx.beginPath(); // 开始一个新的路径
ctx.arc(150, 100, 50, 0, Math.PI * 2); // 绘制一个圆
ctx.fillStyle = 'green';
ctx.fill(); // 填充圆
</script>
</body>
</html>
在这个例子中:
我们首先通过getElementById获取了canvas元素。
然后使用getContext('2d')方法获取2D渲染上下文。这个方法返回一个CanvasRenderingContext2D对象,我们将它存储在ctx变量中。
获取上下文后,我们就可以使用它提供的各种方法进行绘图了。例如:fillRect(): 绘制填充矩形;设置各种绘图属性,如fillStyle(填充颜色)。
运行效果如下:
使用 2D 上下文绘图
通过 2D 上下文,可以使用各种方法来绘制图形、文本和图像。以下是一些常用的方法和操作:
1. 绘制矩形
// 填充矩形
context.fillStyle = 'blue';
context.fillRect(10, 10, 100, 100);
// 描边矩形
context.strokeStyle = 'red';
context.strokeRect(130, 10, 100, 100);
// 清除矩形区域
context.clearRect(50, 50, 60, 60);
2. 绘制路径
context.beginPath();
context.moveTo(150, 50);
context.lineTo(200, 50);
context.lineTo(175, 100);
context.closePath();
context.stroke();
3. 绘制圆和弧
context.beginPath();
context.arc(75, 75, 50, 0, Math.PI * 2, true); // 外圆
context.moveTo(110, 75);
context.arc(75, 75, 35, 0, Math.PI, false); // 口 (顺时针)
context.moveTo(65, 65);
context.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼
context.moveTo(95, 65);
context.arc(90, 65, 5, 0, Math.PI * 2, true); // 右眼
context.stroke();
4. 绘制文本
context.font = '30px Arial';
context.fillText('Hello Canvas', 50, 50);
context.strokeText('Hello Canvas', 50, 100);
5. 绘制图像
let img = new Image();
img.onload = function() {
context.drawImage(img, 0, 0);
};
img.src = 'path/to/your/image.jpg';
处理动画
通过不断更新画布并重绘,可以实现动画效果。典型的动画实现步骤如下:
清除画布
更新图形位置
重绘图形
使用 requestAnimationFrame 调度确保这个过程以一种高效且平滑的方式持续进行下一次绘制。
例、创建了一个在画布上四个方向上(上、下、左、右)移动的红色小球,源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>画布动画示例</title>
<style>
canvas { border: 1px solid black; }
</style>
</head>
<body>
<canvas id="myCanvas" width="400" height="200"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = canvas.width / 2;
let y = canvas.height / 2;
let dx = 2;
let dy = -2;
let radius = 20;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
ctx.closePath();
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
// 更新球的位置
x += dx;
y += dy;
// 碰到左右边界时改变水平方向
if (x + radius > canvas.width || x - radius < 0) {
dx = -dx;
}
// 碰到上下边界时改变垂直方向
if (y + radius > canvas.height || y - radius < 0) {
dy = -dy;
}
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>
交互操作
结合事件监听器,可以使 <canvas> 元素响应用户的交互,例如允许用户通过点击画布来添加一个新的球,在四个方向上(上、下、左、右)移动,并且移动鼠标改变背景颜色,源码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>交互式画布动画</title>
<style>
canvas { border: 1px solid black; cursor: pointer; }
</style>
</head>
<body>
<canvas id="myCanvas" width="400" height="200"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let balls = [];
function createBall(x, y) {
return {
x: x,
y: y,
dx: (Math.random() - 0.5) * 4,
dy: (Math.random() - 0.5) * 4,
radius: 10,
color: `rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255})`,
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
},
update: function() {
if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {
this.dx = -this.dx;
}
if (this.y + this.radius > canvas.height || this.y - this.radius < 0) {
this.dy = -this.dy;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
};
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
balls.forEach(ball => ball.update());
requestAnimationFrame(animate);
}
// 事件监听器:点击画布添加新球
canvas.addEventListener('click', function(event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
balls.push(createBall(x, y));
});
// 事件监听器:鼠标移动时改变背景色
canvas.addEventListener('mousemove', function(event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const r = Math.round(x / canvas.width * 255);
const g = Math.round(y / canvas.height * 255);
const b = Math.round((x + y) / (canvas.width + canvas.height) * 255);
canvas.style.backgroundColor = `rgb(${r},${g},${b})`;
});
animate();
</script>
</body>
</html>
使用class(类)的方法通常被认为是更现代和更易于维护的方法,特别是对于更复杂的对象和继承结构。使用 class 来实现前面例子效果的源码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>交互式画布动画</title>
<style>
canvas { border: 1px solid black; cursor: pointer; }
</style>
</head>
<body>
<canvas id="myCanvas" width="400" height="200"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let balls = [];
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
this.dx = (Math.random() - 0.5) * 4;
this.dy = (Math.random() - 0.5) * 4;
this.radius = 10;
this.color = `rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255})`;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
update() {
if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {
this.dx = -this.dx;
}
if (this.y + this.radius > canvas.height || this.y - this.radius < 0) {
this.dy = -this.dy;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
balls.forEach(ball => ball.update());
requestAnimationFrame(animate);
}
// 事件监听器:点击画布添加新球
canvas.addEventListener('click', function(event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
balls.push(new Ball(x, y));
});
// 事件监听器:鼠标移动时改变背景色
canvas.addEventListener('mousemove', function(event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const r = Math.round(x / canvas.width * 255);
const g = Math.round(y / canvas.height * 255);
const b = Math.round((x + y) / (canvas.width + canvas.height) * 255);
canvas.style.backgroundColor = `rgb(${r},${g},${b})`;
});
animate();
</script>
</body>
</html>
清除 canvas
要清除整个canvas,可以使用:
ctx.clearRect(0, 0, canvas.width, canvas.height);
保存和恢复canvas状态
canvas提供了保存和恢复绘图状态的方法:
ctx.save(); // 保存当前状态
// 进行一些绘制操作
ctx.restore(); // 恢复到之前保存的状态
canvas坐标系变换
虽然默认坐标系是固定的,但我们可以通过以下方法来改变绘图上下文的坐标系:
a. 平移 (Translate):
ctx.translate(x, y);
这会将坐标系的原点移动到 (x, y) 点。
b. 旋转 (Rotate):
ctx.rotate(angle);
这会围绕当前原点旋转坐标系,angle 是弧度。
c. 缩放 (Scale):
ctx.scale(scaleX, scaleY);
这会按照给定的比例因子缩放坐标系。
示例:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>变换示例</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="200"></canvas>
<script>
let canvas = document.getElementById('myCanvas');
let ctx = canvas.getContext('2d');
// 保存初始状态
ctx.save();
// 平移坐标系
ctx.translate(100, 100);
// 旋转坐标系
ctx.rotate(Math.PI / 4);
// 在新坐标系中绘制一个矩形
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 50);
// 恢复到初始状态
ctx.restore();
// 在原始坐标系中绘制一个圆
ctx.beginPath();
ctx.arc(50, 50, 25, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();
</script>
</body>
</html>
顺便一提,<canvas> 还支持 3D 绘图,通过获取 WebGL 上下文,可以进行 3D 渲染。当涉及到完整的 WebGL 代码时,它通常会变得相当长且复杂,因为需要设置顶点着色器、片段着色器、顶点数据、索引数据、矩阵变换等等,对此我没有深入学习,就不多说了。
进一步学习,可见:
用HTML5的<canvas>元素实现刮刮乐游戏https://blog.youkuaiyun.com/cnds123/article/details/136388100
HTML5 canvas 图形标签的用法快速入门 https://blog.youkuaiyun.com/cnds123/article/details/112916903
OK!