HTML Canvas
在本章节中,我们将学习HTML Canvas API的基本概念、语法和用法。Canvas是HTML5中引入的一个重要元素,它允许我们使用JavaScript在网页中绘制图形、创建动画和实现交互效果。
1. 什么是HTML Canvas?
Canvas是HTML5中引入的一个用于绘制图形的元素。它提供了一个矩形区域,我们可以使用JavaScript通过Canvas API在这个区域内绘制图形、文本、图像和动画。
与SVG不同,Canvas是基于位图的,它绘制的图形是由像素组成的,放大后会失真。但Canvas提供了更强大的绘制能力,适合创建复杂的动画和游戏。
1.1 Canvas的特点
- 基于位图:绘制的图形由像素组成,放大后会失真
- 基于JavaScript:所有绘制操作都通过JavaScript完成
- 高性能:适合创建复杂的动画和游戏
- 直接操作像素:可以直接读写像素数据
- 支持多种绘制操作:可以绘制图形、文本、图像等
- 支持动画:可以通过定时器或requestAnimationFrame创建动画
- 支持交互:可以响应鼠标和键盘事件
1.2 Canvas的应用场景
- 游戏开发
- 数据可视化和图表
- 图像处理和滤镜效果
- 动画和交互效果
- 实时视频处理
- 模拟和仿真
2. Canvas的基本用法
2.1 创建Canvas元素
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Canvas基本示例</title>
</head>
<body>
<h1>Canvas基本示例</h1>
<!-- 创建一个宽度为400px,高度为300px的Canvas -->
<canvas id="myCanvas" width="400" height="300" style="border: 1px solid black;"></canvas>
<script>
// 获取Canvas元素
const canvas = document.getElementById('myCanvas');
// 获取2D绘图上下文
const ctx = canvas.getContext('2d');
// 在Canvas上绘制图形
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 150, 100);
</script>
</body>
</html>说明:
canvas元素需要设置width和height属性来定义绘图区域的大小- 通过
getContext('2d')方法获取2D绘图上下文,这是绘制2D图形的主要接口 - 目前Canvas只支持2D绘图,3D绘图需要使用WebGL
2.2 Canvas坐标系
Canvas使用笛卡尔坐标系,原点(0, 0)位于Canvas的左上角,x轴向右延伸,y轴向下延伸。
(0,0) ---------------------> x轴
|
|
|
|
|
v
y轴3. Canvas的基本绘图操作
3.1 绘制矩形
Canvas提供了三种绘制矩形的方法:
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 1. 填充矩形
ctx.fillStyle = 'red'; // 设置填充颜色
ctx.fillRect(50, 50, 100, 80); // x, y, width, height
// 2. 描边矩形
ctx.strokeStyle = 'blue'; // 设置描边颜色
ctx.lineWidth = 3; // 设置线条宽度
ctx.strokeRect(200, 50, 100, 80); // x, y, width, height
// 3. 清空矩形区域
ctx.clearRect(70, 70, 60, 40); // x, y, width, height3.2 绘制路径
路径是Canvas中最强大的绘制功能之一,它允许我们创建复杂的图形。绘制路径的基本步骤:
- 开始路径:
beginPath() - 移动起点:
moveTo(x, y) - 绘制路径:使用各种路径绘制方法
- 闭合路径:
closePath()(可选) - 填充或描边路径:
fill()或stroke()
3.2.1 绘制直线
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制一条直线
ctx.beginPath();
ctx.moveTo(50, 150); // 起点
ctx.lineTo(200, 150); // 终点
ctx.strokeStyle = 'green';
ctx.lineWidth = 2;
ctx.stroke(); // 描边
// 绘制多条直线
ctx.beginPath();
ctx.moveTo(50, 180);
ctx.lineTo(150, 250);
ctx.lineTo(250, 180);
ctx.lineTo(350, 250);
ctx.strokeStyle = 'purple';
ctx.lineWidth = 3;
ctx.stroke();
// 绘制闭合路径
ctx.beginPath();
ctx.moveTo(50, 280);
ctx.lineTo(150, 200);
ctx.lineTo(250, 280);
ctx.closePath(); // 闭合路径,连接终点和起点
ctx.fillStyle = 'orange';
ctx.fill(); // 填充3.2.2 绘制圆弧和圆形
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制圆弧
// arc(x, y, radius, startAngle, endAngle, anticlockwise)
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI, false); // 半圆
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制完整的圆形
ctx.beginPath();
ctx.arc(250, 100, 50, 0, Math.PI * 2, false); // 完整的圆
ctx.fillStyle = 'blue';
ctx.fill(); // 填充
// 绘制扇形
ctx.beginPath();
ctx.moveTo(100, 220); // 圆心
ctx.arc(100, 220, 50, 0, Math.PI / 2, false); // 扇形弧
ctx.closePath(); // 闭合路径
ctx.fillStyle = 'green';
ctx.fill();
// 绘制圆弧的另一种方法:arcTo
ctx.beginPath();
ctx.moveTo(200, 220);
ctx.arcTo(300, 220, 300, 320, 50);
ctx.strokeStyle = 'purple';
ctx.lineWidth = 3;
ctx.stroke();3.2.3 绘制贝塞尔曲线
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制二次贝塞尔曲线
// quadraticCurveTo(cp1x, cp1y, x, y)
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.quadraticCurveTo(150, 20, 250, 100);
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制三次贝塞尔曲线
// bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
ctx.beginPath();
ctx.moveTo(50, 200);
ctx.bezierCurveTo(100, 100, 200, 300, 350, 200);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.stroke();3.3 绘制文本
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 填充文本
ctx.font = '30px Arial'; // 设置字体
ctx.fillStyle = 'black'; // 设置填充颜色
ctx.textAlign = 'left'; // 设置文本对齐方式
ctx.fillText('填充文本', 50, 50); // 文本内容, x, y
// 描边文本
ctx.font = '24px Verdana';
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.strokeText('描边文本', 50, 100);
// 文本对齐方式
ctx.font = '20px Arial';
ctx.fillStyle = 'blue';
ctx.textAlign = 'left';
ctx.fillText('左对齐', 200, 150);
ctx.textAlign = 'center';
ctx.fillText('居中对齐', 200, 180);
ctx.textAlign = 'right';
ctx.fillText('右对齐', 200, 210);
// 测量文本宽度
const text = '测量文本宽度';
const textWidth = ctx.measureText(text).width;
ctx.fillText(text, 50, 250);
ctx.strokeStyle = 'green';
ctx.beginPath();
ctx.moveTo(50, 260);
ctx.lineTo(50 + textWidth, 260);
ctx.stroke();3.4 绘制图像
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 创建图像对象
const img = new Image();
img.src = 'image.jpg'; // 图像URL
// 图像加载完成后绘制
img.onload = function() {
// 绘制完整图像
ctx.drawImage(img, 50, 50); // 图像对象, x, y
// 绘制缩放后的图像
ctx.drawImage(img, 200, 50, 100, 80); // 图像对象, x, y, width, height
// 绘制图像的一部分
// drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
ctx.drawImage(img, 0, 0, 100, 100, 50, 200, 150, 150);
};4. Canvas的样式和颜色
4.1 填充样式
// 纯色填充
ctx.fillStyle = 'red';
ctx.fillStyle = '#ff0000';
ctx.fillStyle = 'rgb(255, 0, 0)';
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // 带透明度
// 渐变填充
// 线性渐变
const linearGradient = ctx.createLinearGradient(0, 0, 200, 0); // x1, y1, x2, y2
linearGradient.addColorStop(0, 'red');
linearGradient.addColorStop(0.5, 'yellow');
linearGradient.addColorStop(1, 'green');
ctx.fillStyle = linearGradient;
// 径向渐变
const radialGradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 100); // cx1, cy1, r1, cx2, cy2, r2
radialGradient.addColorStop(0, 'white');
radialGradient.addColorStop(1, 'blue');
ctx.fillStyle = radialGradient;
// 图案填充
const patternCanvas = document.createElement('canvas');
patternCanvas.width = 20;
patternCanvas.height = 20;
const patternCtx = patternCanvas.getContext('2d');
patternCtx.fillStyle = 'black';
patternCtx.fillRect(0, 0, 10, 10);
patternCtx.fillRect(10, 10, 10, 10);
const pattern = ctx.createPattern(patternCanvas, 'repeat');
ctx.fillStyle = pattern;4.2 描边样式
// 描边颜色
ctx.strokeStyle = 'blue';
// 线条宽度
ctx.lineWidth = 5;
// 线条端点样式
ctx.lineCap = 'butt'; // 默认值
ctx.lineCap = 'round'; // 圆形端点
ctx.lineCap = 'square'; // 方形端点
// 线条连接样式
ctx.lineJoin = 'miter'; // 默认值,尖角连接
ctx.lineJoin = 'round'; // 圆角连接
ctx.lineJoin = 'bevel'; // 斜角连接
// 虚线
ctx.setLineDash([5, 15]); // 虚线图案:实线长度, 间隙长度
ctx.lineDashOffset = 0; // 虚线偏移量5. Canvas的变换
5.1 平移变换
ctx.save(); // 保存当前状态
ctx.translate(100, 50); // 平移原点到(100, 50)
ctx.fillRect(0, 0, 100, 80); // 绘制矩形,坐标相对于新原点
ctx.restore(); // 恢复之前保存的状态5.2 旋转变换
ctx.save();
ctx.translate(150, 150); // 将原点移到旋转中心
ctx.rotate(Math.PI / 4); // 旋转45度(弧度)
ctx.fillRect(-50, -40, 100, 80); // 绘制矩形,中心点在原点
ctx.restore();5.3 缩放变换
ctx.save();
ctx.scale(2, 1.5); // x方向缩放2倍,y方向缩放1.5倍
ctx.fillRect(50, 50, 100, 80);
ctx.restore();5.4 矩阵变换
ctx.save();
ctx.transform(1, 0, 0, 1, 100, 50); // 平移变换
// transform(a, b, c, d, e, f) 对应矩阵:
// [a c e]
// [b d f]
// [0 0 1]
ctx.restore();5.5 状态保存和恢复
Canvas提供了save()和restore()方法来保存和恢复绘图状态,包括:
- 变换矩阵
- 填充和描边样式
- 线条样式
- 文本样式
- 裁剪路径
ctx.save(); // 保存当前状态
// 进行各种绘图操作和变换
ctx.restore(); // 恢复之前保存的状态6. Canvas的动画
6.1 使用setInterval创建动画
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = 50;
let speed = 2;
// 每16毫秒(约60fps)更新一次动画
setInterval(function() {
// 清空Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新位置
x += speed;
// 边界检测
if (x + 100 > canvas.width || x < 0) {
speed = -speed;
}
// 绘制矩形
ctx.fillStyle = 'red';
ctx.fillRect(x, 150, 100, 80);
}, 16);6.2 使用requestAnimationFrame创建动画
requestAnimationFrame是创建动画的推荐方法,它可以根据浏览器的刷新频率自动调整动画速度,提供更流畅的动画效果。
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = 50;
let y = 150;
let speedX = 2;
let speedY = 1;
// 动画函数
function animate() {
// 清空Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新位置
x += speedX;
y += speedY;
// 边界检测
if (x + 50 > canvas.width || x < 0) {
speedX = -speedX;
}
if (y + 50 > canvas.height || y < 0) {
speedY = -speedY;
}
// 绘制圆形
ctx.beginPath();
ctx.arc(x, y, 25, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();
// 请求下一帧动画
requestAnimationFrame(animate);
}
// 开始动画
animate();7. Canvas的交互
7.1 鼠标事件
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 鼠标移动事件
canvas.addEventListener('mousemove', function(event) {
// 获取鼠标在Canvas中的坐标
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// 清空Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制跟随鼠标的圆形
ctx.beginPath();
ctx.arc(x, y, 30, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
});
// 鼠标点击事件
canvas.addEventListener('click', function(event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// 绘制随机颜色的圆形
const colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
const randomColor = colors[Math.floor(Math.random() * colors.length)];
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2);
ctx.fillStyle = randomColor;
ctx.fill();
});7.2 键盘事件
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = canvas.width / 2;
let y = canvas.height / 2;
const speed = 5;
// 键盘按下事件
window.addEventListener('keydown', function(event) {
// 清空Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 根据按键移动位置
switch(event.key) {
case 'ArrowLeft':
x -= speed;
break;
case 'ArrowRight':
x += speed;
break;
case 'ArrowUp':
y -= speed;
break;
case 'ArrowDown':
y += speed;
break;
}
// 绘制矩形
ctx.fillStyle = 'blue';
ctx.fillRect(x - 25, y - 25, 50, 50);
});8. Canvas的高级功能
8.1 像素操作
Canvas允许我们直接读写像素数据,这对于图像处理和滤镜效果非常有用。
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制一个矩形
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 200, 150);
// 获取像素数据
const imageData = ctx.getImageData(50, 50, 200, 150);
const data = imageData.data; // 像素数据数组,格式:[r, g, b, a, r, g, b, a, ...]
// 反转颜色
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // 红色通道
data[i + 1] = 255 - data[i + 1]; // 绿色通道
data[i + 2] = 255 - data[i + 2]; // 蓝色通道
// data[i + 3] 是 alpha 通道,保持不变
}
// 将修改后的像素数据绘制回Canvas
ctx.putImageData(imageData, 300, 50);8.2 合成操作
合成操作控制如何将新绘制的图形与已存在的图形结合。
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制第一个圆形
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(150, 150, 80, 0, Math.PI * 2);
ctx.fill();
// 设置合成模式
ctx.globalCompositeOperation = 'source-over'; // 默认值,新图形覆盖旧图形
// 其他合成模式:destination-over, source-in, destination-in, source-out, destination-out, source-atop, destination-atop, lighter, copy, xor
// 绘制第二个圆形
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(200, 150, 80, 0, Math.PI * 2);
ctx.fill();8.3 裁剪路径
裁剪路径用于限制绘制区域,只有在裁剪路径内的图形才会被绘制。
// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 创建裁剪路径(圆形)
ctx.beginPath();
ctx.arc(200, 150, 100, 0, Math.PI * 2);
ctx.clip(); // 设置裁剪路径
// 绘制超出裁剪路径的矩形
ctx.fillStyle = 'yellow';
ctx.fillRect(50, 50, 300, 200);
// 绘制文本
ctx.font = '30px Arial';
ctx.fillStyle = 'blue';
ctx.textAlign = 'center';
ctx.fillText('裁剪路径示例', 200, 150);9. Canvas的最佳实践
9.1 性能优化
- 减少绘制操作次数
- 使用
requestAnimationFrame创建动画 - 避免在动画中使用
getImageData和putImageData等昂贵操作 - 使用离屏Canvas绘制复杂图形
- 缓存静态内容
- 避免频繁的状态切换
- 使用合适的Canvas尺寸
9.2 响应式设计
// 获取Canvas元素
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 设置Canvas为响应式
function resizeCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
// 重新绘制内容
draw();
}
// 绘制函数
function draw() {
// 绘制内容
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 初始化
resizeCanvas();
// 窗口大小改变时重新调整Canvas大小
window.addEventListener('resize', resizeCanvas);9.3 可访问性
- 为Canvas添加描述性文本
- 使用ARIA属性
- 提供替代内容
- 确保键盘可访问
9.4 调试技巧
- 使用浏览器的开发者工具检查Canvas
- 在Canvas上绘制辅助线和网格
- 使用
console.log输出调试信息 - 分段测试复杂绘制代码
10. 常见问题解答
Q: Canvas和SVG有什么区别?
A: Canvas是基于位图的,适合复杂动画和游戏;SVG是基于矢量的,适合静态图形和简单动画。
Q: Canvas支持3D绘制吗?
A: 原生Canvas只支持2D绘制,3D绘制需要使用WebGL。
Q: 如何保存Canvas绘制的图像?
A: 可以使用toDataURL()方法将Canvas内容转换为Data URL,然后创建下载链接。
Q: 如何处理Canvas的高清显示?
A: 可以通过以下方式处理:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
canvas.style.width = rect.width + 'px';
canvas.style.height = rect.height + 'px';Q: Canvas的绘制性能如何?
A: Canvas的绘制性能非常高,适合创建复杂的动画和游戏,但需要注意优化绘制操作。
Q: 如何在Canvas中播放视频?
A: 可以使用drawImage()方法将视频帧绘制到Canvas上。
11. 练习项目
创建一个HTML文件,包含以下内容:
- 页面标题为"HTML Canvas练习"
- 页面头部包含必要的元标签(字符集、视口等)
- 创建一个Canvas元素,设置合适的宽度和高度
- 实现以下Canvas功能:
- 绘制基本图形(矩形、圆形、直线等)
- 绘制路径和复杂图形
- 绘制文本和图像
- 添加样式和颜色
- 实现简单的动画效果
- 添加鼠标和键盘交互
- 创建一个简单的Canvas游戏,如贪吃蛇或打砖块
- 确保页面在不同设备上都能正常显示
在浏览器中打开文件,验证Canvas功能的实现效果
测试动画和交互效果
优化Canvas性能,确保流畅运行
检查HTML代码是否符合标准
12. 小结
- Canvas是HTML5中用于绘制图形的元素,基于位图,使用JavaScript进行绘制
- Canvas提供了2D绘图上下文,可以绘制图形、文本、图像等
- 基本绘图操作包括绘制矩形、路径、文本和图像
- 可以使用样式和颜色美化绘制内容
- 支持变换操作,如平移、旋转、缩放
- 可以创建动画和实现交互效果
- 支持像素操作、合成操作和裁剪路径
- 最佳实践包括性能优化、响应式设计和可访问性考虑
- Canvas适合创建游戏、数据可视化、动画和交互效果
在下一章节中,我们将学习HTML音频,了解如何在网页中添加和控制音频内容。