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元素需要设置widthheight属性来定义绘图区域的大小
  • 通过getContext(&#39;2d&#39;)方法获取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, height

3.2 绘制路径

路径是Canvas中最强大的绘制功能之一,它允许我们创建复杂的图形。绘制路径的基本步骤:

  1. 开始路径:beginPath()
  2. 移动起点:moveTo(x, y)
  3. 绘制路径:使用各种路径绘制方法
  4. 闭合路径:closePath()(可选)
  5. 填充或描边路径: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创建动画
  • 避免在动画中使用getImageDataputImageData等昂贵操作
  • 使用离屏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. 练习项目

  1. 创建一个HTML文件,包含以下内容:

    • 页面标题为"HTML Canvas练习"
    • 页面头部包含必要的元标签(字符集、视口等)
    • 创建一个Canvas元素,设置合适的宽度和高度
    • 实现以下Canvas功能:
      • 绘制基本图形(矩形、圆形、直线等)
      • 绘制路径和复杂图形
      • 绘制文本和图像
      • 添加样式和颜色
      • 实现简单的动画效果
      • 添加鼠标和键盘交互
    • 创建一个简单的Canvas游戏,如贪吃蛇或打砖块
    • 确保页面在不同设备上都能正常显示
  2. 在浏览器中打开文件,验证Canvas功能的实现效果

  3. 测试动画和交互效果

  4. 优化Canvas性能,确保流畅运行

  5. 检查HTML代码是否符合标准

12. 小结

  • Canvas是HTML5中用于绘制图形的元素,基于位图,使用JavaScript进行绘制
  • Canvas提供了2D绘图上下文,可以绘制图形、文本、图像等
  • 基本绘图操作包括绘制矩形、路径、文本和图像
  • 可以使用样式和颜色美化绘制内容
  • 支持变换操作,如平移、旋转、缩放
  • 可以创建动画和实现交互效果
  • 支持像素操作、合成操作和裁剪路径
  • 最佳实践包括性能优化、响应式设计和可访问性考虑
  • Canvas适合创建游戏、数据可视化、动画和交互效果

在下一章节中,我们将学习HTML音频,了解如何在网页中添加和控制音频内容。

« 上一篇 HTML SVG 下一篇 » HTML音频