HTML5 Canvas 完全指南:从基础到高级动画与游戏开发 | 前端图形编程教程

2025-08-24 0 210

掌握Canvas API,创建令人惊叹的图形、动画和交互式游戏

Canvas 简介

HTML5 Canvas 是一个强大的图形绘制API,允许开发者使用JavaScript在网页上动态生成和操作图形、动画和交互式内容。与SVG不同,Canvas是基于位图的,这意味着它通过像素操作来绘制图形。

Canvas广泛应用于数据可视化、游戏开发、图像处理、广告创意和交互式教育内容等领域。它的性能优势使其成为复杂图形应用的理想选择。

基础用法与绘制

要使用Canvas,首先需要在HTML中创建canvas元素,然后通过JavaScript获取其上下文进行绘制。

创建Canvas元素

<canvas id="myCanvas" width="800" height="600">
  您的浏览器不支持Canvas,请升级或更换浏览器。
</canvas>

获取上下文与基本绘制

// 获取Canvas元素和上下文
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 检查浏览器支持
if (!ctx) {
  console.error('您的浏览器不支持Canvas');
}

// 绘制矩形
ctx.fillStyle = 'blue';      // 设置填充颜色
ctx.fillRect(50, 50, 200, 100); // 绘制填充矩形

ctx.strokeStyle = 'red';     // 设置描边颜色
ctx.lineWidth = 3;           // 设置线宽
ctx.strokeRect(300, 50, 200, 100); // 绘制描边矩形

// 清除部分区域
ctx.clearRect(100, 75, 100, 50);

颜色与样式

// 使用颜色名称
ctx.fillStyle = 'red';

// 使用十六进制值
ctx.fillStyle = '#ff0000';

// 使用RGB
ctx.fillStyle = 'rgb(255, 0, 0)';

// 使用RGBA(带透明度)
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';

// 使用渐变色
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(50, 50, 200, 100);

形状与路径绘制

Canvas提供了丰富的路径API,可以绘制各种复杂形状。

基本路径绘制

// 开始新路径
ctx.beginPath();

// 移动到起点
ctx.moveTo(50, 50);

// 绘制直线
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);

// 闭合路径(可选)
ctx.closePath();

// 设置样式并绘制
ctx.fillStyle = 'green';
ctx.fill();

ctx.strokeStyle = 'darkgreen';
ctx.lineWidth = 2;
ctx.stroke();

绘制圆形与弧形

// 绘制完整圆形
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.fillStyle = 'orange';
ctx.fill();

// 绘制半圆
ctx.beginPath();
ctx.arc(350, 200, 50, 0, Math.PI);
ctx.fillStyle = 'purple';
ctx.fill();

// 绘制扇形
ctx.beginPath();
ctx.moveTo(500, 200);
ctx.arc(500, 200, 50, 0, Math.PI / 2);
ctx.closePath();
ctx.fillStyle = 'teal';
ctx.fill();

贝塞尔曲线

// 二次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(50, 300);
ctx.quadraticCurveTo(150, 250, 250, 300);
ctx.strokeStyle = 'blue';
ctx.stroke();

// 三次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(300, 300);
ctx.bezierCurveTo(350, 250, 450, 350, 500, 300);
ctx.strokeStyle = 'red';
ctx.stroke();

文本与图像处理

Canvas不仅可以绘制形状,还可以渲染文本和处理图像。

文本渲染

// 设置文本样式
ctx.font = '30px Arial';
ctx.fillStyle = 'navy';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

// 绘制填充文本
ctx.fillText('Hello Canvas', 400, 100);

// 绘制描边文本
ctx.strokeStyle = 'darkred';
ctx.lineWidth = 1;
ctx.strokeText('Hello Canvas', 400, 150);

// 测量文本宽度
const text = 'Hello Canvas';
const metrics = ctx.measureText(text);
console.log(`文本宽度: ${metrics.width}px`);

图像绘制与处理

// 创建图像对象
const img = new Image();

// 设置加载完成后的回调
img.onload = function() {
  // 绘制原始图像
  ctx.drawImage(img, 50, 300, 100, 100);
  
  // 绘制裁剪后的图像
  ctx.drawImage(
    img, 
    50, 50, 100, 100,  // 源图像裁剪区域
    200, 300, 150, 150 // 目标绘制区域
  );
};

// 设置图像源(必须在onload后设置)
img.src = 'image.jpg';

像素操作

// 获取像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

// 修改像素数据(创建灰度效果)
for (let i = 0; i < data.length; i += 4) {
  const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
  data[i] = avg;     // 红色通道
  data[i + 1] = avg; // 绿色通道
  data[i + 2] = avg; // 蓝色通道
}

// 将修改后的数据放回Canvas
ctx.putImageData(imageData, 0, 0);

动画实现技术

Canvas动画基于连续重绘原理,通常使用requestAnimationFrame实现。

基本动画循环

let x = 50;
let speed = 2;

function animate() {
  // 清除画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 更新位置
  x += speed;
  
  // 边界检测
  if (x > canvas.width - 50 || x < 50) {
    speed = -speed;
  }
  
  // 绘制图形
  ctx.beginPath();
  ctx.arc(x, 100, 50, 0, Math.PI * 2);
  ctx.fillStyle = 'coral';
  ctx.fill();
  
  // 继续动画循环
  requestAnimationFrame(animate);
}

// 启动动画
animate();

复杂动画示例

const particles = [];

// 创建粒子
function createParticles(x, y, count) {
  for (let i = 0; i < count; i++) {
    particles.push({
      x: x,
      y: y,
      size: Math.random() * 5 + 1,
      speedX: Math.random() * 6 - 3,
      speedY: Math.random() * 6 - 3,
      color: `hsl(${Math.random() * 360}, 100%, 50%)`
    });
  }
}

// 粒子动画
function animateParticles() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  for (let i = 0; i < particles.length; i++) {
    const p = particles[i];
    
    // 更新位置
    p.x += p.speedX;
    p.y += p.speedY;
    
    // 缩小粒子
    p.size -= 0.1;
    
    // 绘制粒子
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fillStyle = p.color;
    ctx.fill();
    
    // 移除消失的粒子
    if (p.size <= 0.2) {
      particles.splice(i, 1);
      i--;
    }
  }
  
  // 添加新粒子
  if (particles.length < 100) {
    createParticles(canvas.width / 2, canvas.height / 2, 5);
  }
  
  requestAnimationFrame(animateParticles);
}

// 启动粒子动画
animateParticles();

交互与事件处理

Canvas本身不支持内部元素的事件监听,但可以通过数学计算实现交互效果。

鼠标交互

// 存储交互对象
const circles = [
  { x: 100, y: 100, radius: 30, color: 'red' },
  { x: 200, y: 200, radius: 40, color: 'blue' },
  { x: 300, y: 300, radius: 50, color: 'green' }
];

// 绘制所有圆形
function drawCircles() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  circles.forEach(circle => {
    ctx.beginPath();
    ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
    ctx.fillStyle = circle.color;
    ctx.fill();
  });
}

// 检测鼠标点击
canvas.addEventListener('click', function(event) {
  const rect = canvas.getBoundingClientRect();
  const mouseX = event.clientX - rect.left;
  const mouseY = event.clientY - rect.top;
  
  // 检查是否点击了某个圆形
  for (let i = 0; i < circles.length; i++) {
    const circle = circles[i];
    const distance = Math.sqrt(
      Math.pow(mouseX - circle.x, 2) + 
      Math.pow(mouseY - circle.y, 2)
    );
    
    if (distance <= circle.radius) {
      console.log(`点击了 ${circle.color} 圆形`);
      circle.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
      drawCircles();
      break;
    }
  }
});

// 初始绘制
drawCircles();

鼠标移动交互

let hoveredCircle = null;

canvas.addEventListener('mousemove', function(event) {
  const rect = canvas.getBoundingClientRect();
  const mouseX = event.clientX - rect.left;
  const mouseY = event.clientY - rect.top;
  
  // 检查鼠标是否在某个圆形上
  let found = false;
  for (let i = 0; i < circles.length; i++) {
    const circle = circles[i];
    const distance = Math.sqrt(
      Math.pow(mouseX - circle.x, 2) + 
      Math.pow(mouseY - circle.y, 2)
    );
    
    if (distance <= circle.radius) {
      document.body.style.cursor = 'pointer';
      found = true;
      
      // 高亮显示
      if (hoveredCircle !== circle) {
        hoveredCircle = circle;
        drawCircles();
        
        // 绘制高亮效果
        ctx.beginPath();
        ctx.arc(circle.x, circle.y, circle.radius + 5, 0, Math.PI * 2);
        ctx.strokeStyle = 'yellow';
        ctx.lineWidth = 3;
        ctx.stroke();
      }
      break;
    }
  }
  
  if (!found) {
    document.body.style.cursor = 'default';
    if (hoveredCircle) {
      hoveredCircle = null;
      drawCircles();
    }
  }
});

实战案例:太空射击游戏

下面通过一个完整的太空射击游戏示例展示Canvas的强大功能:

// 游戏主类
class SpaceShooter {
  constructor() {
    this.canvas = document.getElementById('gameCanvas');
    this.ctx = this.canvas.getContext('2d');
    this.canvas.width = 800;
    this.canvas.height = 600;
    
    this.player = {
      x: this.canvas.width / 2,
      y: this.canvas.height - 50,
      width: 50,
      height: 50,
      speed: 5,
      color: 'blue'
    };
    
    this.bullets = [];
    this.enemies = [];
    this.score = 0;
    this.gameOver = false;
    
    this.keys = {};
    
    // 设置事件监听
    this.setupEventListeners();
    
    // 启动游戏
    this.gameLoop();
    this.spawnEnemies();
  }
  
  setupEventListeners() {
    window.addEventListener('keydown', (e) => {
      this.keys[e.key] = true;
      
      // 发射子弹
      if (e.key === ' ' && !this.gameOver) {
        this.bullets.push({
          x: this.player.x,
          y: this.player.y,
          width: 5,
          height: 15,
          speed: 10,
          color: 'yellow'
        });
      }
      
      // 重新开始游戏
      if (e.key === 'r' && this.gameOver) {
        this.resetGame();
      }
    });
    
    window.addEventListener('keyup', (e) => {
      this.keys[e.key] = false;
    });
  }
  
  resetGame() {
    this.player.x = this.canvas.width / 2;
    this.bullets = [];
    this.enemies = [];
    this.score = 0;
    this.gameOver = false;
    this.gameLoop();
    this.spawnEnemies();
  }
  
  spawnEnemies() {
    if (this.gameOver) return;
    
    // 创建敌人
    this.enemies.push({
      x: Math.random() * (this.canvas.width - 30),
      y: -30,
      width: 30,
      height: 30,
      speed: 2 + Math.random() * 2,
      color: `hsl(${Math.random() * 360}, 100%, 50%)`
    });
    
    // 设置下一次生成时间
    setTimeout(() => this.spawnEnemies(), 1000 - Math.min(this.score, 800));
  }
  
  update() {
    // 移动玩家
    if (this.keys['ArrowLeft'] && this.player.x > 0) {
      this.player.x -= this.player.speed;
    }
    if (this.keys['ArrowRight'] && this.player.x = 0; i--) {
      this.bullets[i].y -= this.bullets[i].speed;
      
      // 移除超出屏幕的子弹
      if (this.bullets[i].y = 0; i--) {
      this.enemies[i].y += this.enemies[i].speed;
      
      // 检查敌人是否超出屏幕
      if (this.enemies[i].y > this.canvas.height) {
        this.enemies.splice(i, 1);
        this.score -= 5; // 扣分
      }
      
      // 检查敌人与玩家碰撞
      if (this.checkCollision(this.player, this.enemies[i])) {
        this.gameOver = true;
        return;
      }
      
      // 检查子弹与敌人碰撞
      for (let j = this.bullets.length - 1; j >= 0; j--) {
        if (this.checkCollision(this.bullets[j], this.enemies[i])) {
          this.enemies.splice(i, 1);
          this.bullets.splice(j, 1);
          this.score += 10;
          break;
        }
      }
    }
  }
  
  checkCollision(obj1, obj2) {
    return obj1.x  obj2.x &&
           obj1.y  obj2.y;
  }
  
  draw() {
    // 清除画布
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    // 绘制玩家
    this.ctx.fillStyle = this.player.color;
    this.ctx.fillRect(this.player.x, this.player.y, this.player.width, this.player.height);
    
    // 绘制子弹
    this.bullets.forEach(bullet => {
      this.ctx.fillStyle = bullet.color;
      this.ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
    });
    
    // 绘制敌人
    this.enemies.forEach(enemy => {
      this.ctx.fillStyle = enemy.color;
      this.ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
    });
    
    // 绘制分数
    this.ctx.fillStyle = 'white';
    this.ctx.font = '20px Arial';
    this.ctx.fillText(`分数: ${this.score}`, 10, 30);
    
    // 游戏结束显示
    if (this.gameOver) {
      this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
      
      this.ctx.fillStyle = 'white';
      this.ctx.font = '40px Arial';
      this.ctx.textAlign = 'center';
      this.ctx.fillText('游戏结束', this.canvas.width / 2, this.canvas.height / 2 - 40);
      this.ctx.font = '20px Arial';
      this.ctx.fillText(`最终分数: ${this.score}`, this.canvas.width / 2, this.canvas.height / 2);
      this.ctx.fillText('按 R 键重新开始', this.canvas.width / 2, this.canvas.height / 2 + 40);
      this.ctx.textAlign = 'left';
    }
  }
  
  gameLoop() {
    if (this.gameOver) return;
    
    this.update();
    this.draw();
    
    requestAnimationFrame(() => this.gameLoop());
  }
}

// 启动游戏
const game = new SpaceShooter();

HTML结构

<!DOCTYPE html>
<html>
<head>
    <title>太空射击游戏</title>
    <style>
        body { margin: 0; overflow: hidden; background: #000; }
        canvas { display: block; margin: 0 auto; background: #001133; }
    </style>
</head>
<body>
    <canvas id="gameCanvas"></canvas>
    <script>
        // 上面的游戏代码放在这里
    </script>
</body>
</html>

性能优化与最佳实践

为了提高Canvas应用的性能,可以遵循以下优化策略:

1. 减少重绘区域

// 只清除变化的部分,而不是整个画布
function optimizedDraw() {
  // 清除需要更新的区域,而不是整个画布
  ctx.clearRect(prevX - 5, prevY - 5, 60, 60);
  ctx.clearRect(currentX - 5, currentY - 5, 60, 60);
  
  // 绘制新内容
  ctx.fillRect(currentX, currentY, 50, 50);
  
  // 保存当前位置
  prevX = currentX;
  prevY = currentY;
}

2. 使用离屏Canvas

// 创建离屏Canvas
const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d');
offscreenCanvas.width = 100;
offscreenCanvas.height = 100;

// 在离屏Canvas上绘制复杂图形
offscreenCtx.beginPath();
offscreenCtx.arc(50, 50, 50, 0, Math.PI * 2);
offscreenCtx.fillStyle = 'red';
offscreenCtx.fill();

// 在主Canvas上重复使用
function drawMultiple() {
  for (let i = 0; i < 10; i++) {
    ctx.drawImage(offscreenCanvas, i * 60, 100);
  }
}

3. 避免浮点数坐标

// 使用整数坐标可以提高性能
ctx.drawImage(image, Math.floor(x), Math.floor(y));

4. 合理使用图层

// 使用多个Canvas叠加实现图层效果
<div style="position: relative;">
  <canvas id="bgCanvas" style="position: absolute; z-index: 1;"></canvas>
  <canvas id="gameCanvas" style="position: absolute; z-index: 2;"></canvas>
  <canvas id="uiCanvas" style="position: absolute; z-index: 3;"></canvas>
</div>

5. 使用WebGL进行3D渲染

// 获取WebGL上下文
const glCanvas = document.getElementById('glCanvas');
const gl = glCanvas.getContext('webgl') || glCanvas.getContext('experimental-webgl');

if (gl) {
  // 使用WebGL进行高性能3D渲染
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
} else {
  console.error('WebGL不支持');
}

HTML5 Canvas 完全指南:从基础到高级动画与游戏开发 | 前端图形编程教程
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 html HTML5 Canvas 完全指南:从基础到高级动画与游戏开发 | 前端图形编程教程 https://www.taomawang.com/web/html/967.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务