活动公告

系统通知
06-18 23:43
系统通知
06-14 00:00
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

HTML5 Canvas核心技术详解从基础到高级应用全面掌握网页图形绘制与动画开发技巧

SunJu_FaceMall

3万

主题

3077

科技点

3万

积分

执行版主

碾压王

积分
32876

塔罗立华奏

执行版主 发表于 2025-9-25 21:40:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

HTML5 Canvas是HTML5标准中最重要的技术之一,它提供了一个通过JavaScript绘制图形的API。Canvas可以用于绘制图形、创建动画、游戏开发、数据可视化、图像处理等多种场景。与SVG不同,Canvas是基于像素的位图绘制技术,一旦绘制完成,就不再记住绘制的图形,而是作为像素存在。这使得Canvas在处理大量图形和复杂动画时具有性能优势。

Canvas基础

Canvas是HTML5中的一个元素,我们可以通过在HTML文档中添加<canvas>标签来创建一个画布。
  1. <canvas id="myCanvas" width="800" height="600"></canvas>
复制代码

上面的代码创建了一个宽度为800像素,高度为600像素的Canvas元素。要在这个Canvas上绘制图形,我们需要获取Canvas的2D渲染上下文:
  1. const canvas = document.getElementById('myCanvas');
  2. const ctx = canvas.getContext('2d');
复制代码

getContext('2d')方法返回一个CanvasRenderingContext2D对象,这个对象提供了大量的方法和属性用于绘制图形。

Canvas使用一个二维坐标系,左上角为原点(0,0),x轴向右延伸,y轴向下延伸。这与我们常见的数学坐标系不同,y轴是向下而不是向上。

基本图形绘制

线条

绘制线条需要用到beginPath()、moveTo()、lineTo()和stroke()方法:
  1. // 开始一条新路径
  2. ctx.beginPath();
  3. // 将画笔移动到起点(50, 50)
  4. ctx.moveTo(50, 50);
  5. // 画一条线到(200, 50)
  6. ctx.lineTo(200, 50);
  7. // 画一条线到(200, 200)
  8. ctx.lineTo(200, 200);
  9. // 描边路径
  10. ctx.stroke();
复制代码

矩形

Canvas提供了三种绘制矩形的方法:fillRect()、strokeRect()和clearRect():
  1. // 填充矩形
  2. ctx.fillRect(50, 50, 200, 100);
  3. // 描边矩形
  4. ctx.strokeRect(300, 50, 200, 100);
  5. // 清除矩形区域
  6. ctx.clearRect(100, 75, 100, 50);
复制代码

圆形

绘制圆形需要使用arc()方法:
  1. ctx.beginPath();
  2. ctx.arc(150, 150, 50, 0, Math.PI * 2); // 圆心(150, 150),半径50,从0到2π
  3. ctx.stroke(); // 描边
  4. // 或者
  5. ctx.fill(); // 填充
复制代码

路径

路径是Canvas中强大的绘图工具,可以创建复杂的形状:
  1. ctx.beginPath();
  2. ctx.moveTo(50, 50);
  3. ctx.lineTo(150, 50);
  4. ctx.lineTo(100, 150);
  5. ctx.closePath(); // 连接起点和终点,形成闭合路径
  6. ctx.stroke();
复制代码

颜色与样式

填充和描边颜色

我们可以使用fillStyle和strokeStyle属性设置填充和描边的颜色:
  1. ctx.fillStyle = 'red';
  2. ctx.fillRect(50, 50, 200, 100);
  3. ctx.strokeStyle = 'blue';
  4. ctx.lineWidth = 5; // 设置线宽
  5. ctx.strokeRect(300, 50, 200, 100);
复制代码

颜色可以使用多种格式指定:

• 颜色名称:’red’, ‘blue’, ‘green’等
• 十六进制:’#FF0000’, ‘#00FF00’, ‘#0000FF’等
• RGB:’rgb(255, 0, 0)‘, ‘rgb(0, 255, 0)’, ‘rgb(0, 0, 255)‘等
• RGBA:’rgba(255, 0, 0, 0.5)‘等,最后一个参数是透明度

渐变

Canvas支持线性渐变和径向渐变:
  1. // 线性渐变
  2. const linearGradient = ctx.createLinearGradient(50, 50, 250, 50);
  3. linearGradient.addColorStop(0, 'red');
  4. linearGradient.addColorStop(1, 'blue');
  5. ctx.fillStyle = linearGradient;
  6. ctx.fillRect(50, 50, 200, 100);
  7. // 径向渐变
  8. const radialGradient = ctx.createRadialGradient(400, 100, 10, 400, 100, 50);
  9. radialGradient.addColorStop(0, 'red');
  10. radialGradient.addColorStop(1, 'blue');
  11. ctx.fillStyle = radialGradient;
  12. ctx.fillRect(350, 50, 100, 100);
复制代码

图案

我们可以使用图像作为填充图案:
  1. const img = new Image();
  2. img.src = 'pattern.png';
  3. img.onload = function() {
  4.     const pattern = ctx.createPattern(img, 'repeat');
  5.     ctx.fillStyle = pattern;
  6.     ctx.fillRect(50, 200, 200, 100);
  7. };
复制代码

文本绘制

Canvas提供了绘制文本的方法:
  1. ctx.font = '30px Arial';
  2. ctx.fillText('Hello World', 50, 50); // 填充文本
  3. ctx.strokeText('Hello World', 50, 100); // 描边文本
复制代码

我们可以设置文本的对齐方式:
  1. ctx.textAlign = 'center'; // 可以是 'left', 'right', 'center', 'start', 'end'
  2. ctx.textBaseline = 'middle'; // 可以是 'top', 'bottom', 'middle', 'alphabetic', 'hanging'
  3. ctx.fillText('Aligned Text', canvas.width / 2, canvas.height / 2);
复制代码

我们还可以测量文本的宽度:
  1. const text = 'Hello World';
  2. const metrics = ctx.measureText(text);
  3. console.log('Text width:', metrics.width);
复制代码

图像处理

绘制图像

Canvas可以绘制图像:
  1. const img = new Image();
  2. img.src = 'image.jpg';
  3. img.onload = function() {
  4.     // 绘制图像到(50, 50),宽度为200,高度为150
  5.     ctx.drawImage(img, 50, 50, 200, 150);
  6. };
复制代码

裁剪

我们可以使用路径来定义裁剪区域:
  1. // 创建一个圆形裁剪区域
  2. ctx.beginPath();
  3. ctx.arc(150, 150, 50, 0, Math.PI * 2);
  4. ctx.clip();
  5. // 现在绘制的任何内容都只会显示在圆形区域内
  6. const img = new Image();
  7. img.src = 'image.jpg';
  8. img.onload = function() {
  9.     ctx.drawImage(img, 100, 100, 100, 100);
  10. };
复制代码

像素操作

Canvas允许我们直接操作像素:
  1. // 获取图像数据
  2. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  3. const data = imageData.data;
  4. // 修改像素数据
  5. for (let i = 0; i < data.length; i += 4) {
  6.     // 红色通道
  7.     data[i] = 255 - data[i]; // 反转红色
  8.     // 绿色通道
  9.     data[i + 1] = 255 - data[i + 1]; // 反转绿色
  10.     // 蓝色通道
  11.     data[i + 2] = 255 - data[i + 2]; // 反转蓝色
  12.     // alpha通道
  13.     // data[i + 3] = 255; // 不修改alpha
  14. }
  15. // 将修改后的图像数据放回Canvas
  16. ctx.putImageData(imageData, 0, 0);
复制代码

变换

Canvas支持多种变换操作,包括平移、旋转和缩放:

平移
  1. ctx.save(); // 保存当前状态
  2. ctx.translate(100, 100); // 将原点移动到(100, 100)
  3. ctx.fillRect(0, 0, 50, 50); // 现在矩形的位置是相对于新原点的
  4. ctx.restore(); // 恢复之前保存的状态
复制代码

旋转
  1. ctx.save();
  2. ctx.translate(150, 150); // 先将原点移动到旋转中心
  3. ctx.rotate(Math.PI / 4); // 旋转45度
  4. ctx.fillRect(-25, -25, 50, 50); // 矩形中心在原点
  5. ctx.restore();
复制代码

缩放
  1. ctx.save();
  2. ctx.translate(150, 150);
  3. ctx.scale(2, 0.5); // x轴放大2倍,y轴缩小到0.5倍
  4. ctx.fillRect(-25, -25, 50, 50);
  5. ctx.restore();
复制代码

组合变换

我们可以组合多种变换:
  1. ctx.save();
  2. ctx.translate(150, 150);
  3. ctx.rotate(Math.PI / 4);
  4. ctx.scale(2, 2);
  5. ctx.fillRect(-25, -25, 50, 50);
  6. ctx.restore();
复制代码

动画基础

使用Canvas创建动画的基本方法是使用requestAnimationFrame函数:
  1. let x = 0;
  2. let y = 0;
  3. let dx = 2;
  4. let dy = 3;
  5. function animate() {
  6.     ctx.clearRect(0, 0, canvas.width, canvas.height);
  7.    
  8.     // 绘制一个移动的球
  9.     ctx.beginPath();
  10.     ctx.arc(x, y, 20, 0, Math.PI * 2);
  11.     ctx.fill();
  12.    
  13.     // 更新位置
  14.     x += dx;
  15.     y += dy;
  16.    
  17.     // 边界检测
  18.     if (x + 20 > canvas.width || x - 20 < 0) {
  19.         dx = -dx;
  20.     }
  21.     if (y + 20 > canvas.height || y - 20 < 0) {
  22.         dy = -dy;
  23.     }
  24.    
  25.     requestAnimationFrame(animate);
  26. }
  27. animate();
复制代码

高级动画技术

缓动函数

缓动函数可以使动画更加自然:
  1. // 线性缓动
  2. function linear(t) {
  3.     return t;
  4. }
  5. // 二次缓入
  6. function easeInQuad(t) {
  7.     return t * t;
  8. }
  9. // 二次缓出
  10. function easeOutQuad(t) {
  11.     return t * (2 - t);
  12. }
  13. // 二次缓入缓出
  14. function easeInOutQuad(t) {
  15.     return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
  16. }
  17. // 使用缓动函数的动画
  18. let startTime = null;
  19. const duration = 2000; // 2秒
  20. const startX = 0;
  21. const endX = canvas.width;
  22. const startY = canvas.height / 2;
  23. const endY = canvas.height / 2;
  24. function animateWithEasing(timestamp) {
  25.     if (!startTime) startTime = timestamp;
  26.     const elapsed = timestamp - startTime;
  27.     const progress = Math.min(elapsed / duration, 1);
  28.    
  29.     // 使用缓动函数
  30.     const easedProgress = easeInOutQuad(progress);
  31.    
  32.     // 计算当前位置
  33.     const x = startX + (endX - startX) * easedProgress;
  34.     const y = startY + (endY - startY) * easedProgress;
  35.    
  36.     ctx.clearRect(0, 0, canvas.width, canvas.height);
  37.     ctx.beginPath();
  38.     ctx.arc(x, y, 20, 0, Math.PI * 2);
  39.     ctx.fill();
  40.    
  41.     if (progress < 1) {
  42.         requestAnimationFrame(animateWithEasing);
  43.     }
  44. }
  45. requestAnimationFrame(animateWithEasing);
复制代码

物理模拟

我们可以模拟简单的物理效果,如重力、摩擦力等:
  1. let ball = {
  2.     x: canvas.width / 2,
  3.     y: 50,
  4.     radius: 20,
  5.     vx: 2, // x方向速度
  6.     vy: 0, // y方向速度
  7.     gravity: 0.2, // 重力加速度
  8.     damping: 0.8, // 反弹衰减
  9.     friction: 0.99 // 摩擦力
  10. };
  11. function animatePhysics() {
  12.     ctx.clearRect(0, 0, canvas.width, canvas.height);
  13.    
  14.     // 应用重力
  15.     ball.vy += ball.gravity;
  16.    
  17.     // 更新位置
  18.     ball.x += ball.vx;
  19.     ball.y += ball.vy;
  20.    
  21.     // 边界检测和反弹
  22.     if (ball.x + ball.radius > canvas.width) {
  23.         ball.x = canvas.width - ball.radius;
  24.         ball.vx = -ball.vx * ball.damping;
  25.     } else if (ball.x - ball.radius < 0) {
  26.         ball.x = ball.radius;
  27.         ball.vx = -ball.vx * ball.damping;
  28.     }
  29.    
  30.     if (ball.y + ball.radius > canvas.height) {
  31.         ball.y = canvas.height - ball.radius;
  32.         ball.vy = -ball.vy * ball.damping;
  33.         
  34.         // 应用摩擦力
  35.         ball.vx *= ball.friction;
  36.     } else if (ball.y - ball.radius < 0) {
  37.         ball.y = ball.radius;
  38.         ball.vy = -ball.vy * ball.damping;
  39.     }
  40.    
  41.     // 绘制球
  42.     ctx.beginPath();
  43.     ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
  44.     ctx.fill();
  45.    
  46.     requestAnimationFrame(animatePhysics);
  47. }
  48. animatePhysics();
复制代码

粒子系统

粒子系统可以创建复杂的效果,如烟花、雨、雪等:
  1. // 粒子类
  2. class Particle {
  3.     constructor(x, y) {
  4.         this.x = x;
  5.         this.y = y;
  6.         this.vx = (Math.random() - 0.5) * 5;
  7.         this.vy = (Math.random() - 0.5) * 5;
  8.         this.radius = Math.random() * 3 + 1;
  9.         this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
  10.         this.alpha = 1;
  11.         this.decay = Math.random() * 0.02 + 0.01;
  12.     }
  13.    
  14.     update() {
  15.         this.x += this.vx;
  16.         this.y += this.vy;
  17.         this.alpha -= this.decay;
  18.     }
  19.    
  20.     draw(ctx) {
  21.         ctx.save();
  22.         ctx.globalAlpha = this.alpha;
  23.         ctx.fillStyle = this.color;
  24.         ctx.beginPath();
  25.         ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
  26.         ctx.fill();
  27.         ctx.restore();
  28.     }
  29. }
  30. // 粒子系统
  31. const particles = [];
  32. function createParticles(x, y, count) {
  33.     for (let i = 0; i < count; i++) {
  34.         particles.push(new Particle(x, y));
  35.     }
  36. }
  37. function animateParticles() {
  38.     ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
  39.     ctx.fillRect(0, 0, canvas.width, canvas.height);
  40.    
  41.     // 随机创建新粒子
  42.     if (Math.random() < 0.1) {
  43.         createParticles(
  44.             Math.random() * canvas.width,
  45.             Math.random() * canvas.height,
  46.             10
  47.         );
  48.     }
  49.    
  50.     // 更新和绘制粒子
  51.     for (let i = particles.length - 1; i >= 0; i--) {
  52.         const particle = particles[i];
  53.         particle.update();
  54.         particle.draw(ctx);
  55.         
  56.         // 移除透明度为0的粒子
  57.         if (particle.alpha <= 0) {
  58.             particles.splice(i, 1);
  59.         }
  60.     }
  61.    
  62.     requestAnimationFrame(animateParticles);
  63. }
  64. animateParticles();
复制代码

性能优化

减少重绘

减少不必要的重绘可以显著提高性能:
  1. // 不好的做法:每一帧都清除整个Canvas
  2. function animate() {
  3.     ctx.clearRect(0, 0, canvas.width, canvas.height);
  4.     // 绘制很多对象
  5.     requestAnimationFrame(animate);
  6. }
  7. // 好的做法:只清除需要更新的区域
  8. function animateOptimized() {
  9.     // 只清除上一帧的对象所在区域
  10.     ctx.clearRect(lastX - radius, lastY - radius, radius * 2, radius * 2);
  11.    
  12.     // 绘制新位置的对象
  13.     ctx.beginPath();
  14.     ctx.arc(x, y, radius, 0, Math.PI * 2);
  15.     ctx.fill();
  16.    
  17.     // 更新上一帧的位置
  18.     lastX = x;
  19.     lastY = y;
  20.    
  21.     requestAnimationFrame(animateOptimized);
  22. }
复制代码

离屏Canvas

使用离屏Canvas可以预先绘制复杂的图形,然后将其绘制到主Canvas上:
  1. // 创建离屏Canvas
  2. const offscreenCanvas = document.createElement('canvas');
  3. offscreenCanvas.width = 200;
  4. offscreenCanvas.height = 200;
  5. const offscreenCtx = offscreenCanvas.getContext('2d');
  6. // 在离屏Canvas上绘制复杂图形
  7. function drawComplexShape(ctx) {
  8.     // 绘制复杂图形
  9.     ctx.fillStyle = 'blue';
  10.     ctx.beginPath();
  11.     ctx.moveTo(100, 0);
  12.     for (let i = 0; i < 8; i++) {
  13.         const angle = (i * Math.PI * 2) / 8;
  14.         const x = 100 + Math.cos(angle) * 80;
  15.         const y = 100 + Math.sin(angle) * 80;
  16.         ctx.lineTo(x, y);
  17.         
  18.         const innerAngle = ((i + 0.5) * Math.PI * 2) / 8;
  19.         const innerX = 100 + Math.cos(innerAngle) * 40;
  20.         const innerY = 100 + Math.sin(innerAngle) * 40;
  21.         ctx.lineTo(innerX, innerY);
  22.     }
  23.     ctx.closePath();
  24.     ctx.fill();
  25. }
  26. drawComplexShape(offscreenCtx);
  27. // 在主Canvas上使用离屏Canvas
  28. function animateWithOffscreen() {
  29.     ctx.clearRect(0, 0, canvas.width, canvas.height);
  30.    
  31.     // 旋转并绘制离屏Canvas
  32.     ctx.save();
  33.     ctx.translate(canvas.width / 2, canvas.height / 2);
  34.     ctx.rotate(Date.now() * 0.001);
  35.     ctx.drawImage(offscreenCanvas, -100, -100);
  36.     ctx.restore();
  37.    
  38.     requestAnimationFrame(animateWithOffscreen);
  39. }
  40. animateWithOffscreen();
复制代码

分层Canvas

对于复杂的场景,可以使用多个Canvas分层渲染:
  1. <div style="position: relative; width: 800px; height: 600px;">
  2.     <canvas id="backgroundLayer" width="800" height="600" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
  3.     <canvas id="gameLayer" width="800" height="600" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
  4.     <canvas id="uiLayer" width="800" height="600" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
  5. </div>
复制代码
  1. const backgroundCanvas = document.getElementById('backgroundLayer');
  2. const backgroundCtx = backgroundCanvas.getContext('2d');
  3. const gameCanvas = document.getElementById('gameLayer');
  4. const gameCtx = gameCanvas.getContext('2d');
  5. const uiCanvas = document.getElementById('uiLayer');
  6. const uiCtx = uiCanvas.getContext('2d');
  7. // 绘制背景(不经常更新)
  8. function drawBackground() {
  9.     backgroundCtx.fillStyle = '#87CEEB'; // 天蓝色
  10.     backgroundCtx.fillRect(0, 0, backgroundCanvas.width, backgroundCanvas.height);
  11.    
  12.     // 绘制地面
  13.     backgroundCtx.fillStyle = '#8B4513'; // 棕色
  14.     backgroundCtx.fillRect(0, backgroundCanvas.height - 50, backgroundCanvas.width, 50);
  15. }
  16. drawBackground();
  17. // 游戏层(经常更新)
  18. let playerX = 400;
  19. let playerY = 300;
  20. function drawGame() {
  21.     gameCtx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
  22.    
  23.     // 绘制玩家
  24.     gameCtx.fillStyle = 'red';
  25.     gameCtx.beginPath();
  26.     gameCtx.arc(playerX, playerY, 20, 0, Math.PI * 2);
  27.     gameCtx.fill();
  28. }
  29. // UI层(不经常更新)
  30. function drawUI() {
  31.     uiCtx.fillStyle = 'white';
  32.     uiCtx.font = '20px Arial';
  33.     uiCtx.fillText('Score: 0', 10, 30);
  34. }
  35. drawUI();
  36. // 游戏循环
  37. function gameLoop() {
  38.     // 更新游戏状态
  39.     playerX += (Math.random() - 0.5) * 5;
  40.     playerY += (Math.random() - 0.5) * 5;
  41.    
  42.     // 边界检查
  43.     playerX = Math.max(20, Math.min(gameCanvas.width - 20, playerX));
  44.     playerY = Math.max(20, Math.min(gameCanvas.height - 70, playerY));
  45.    
  46.     // 只重绘游戏层
  47.     drawGame();
  48.    
  49.     requestAnimationFrame(gameLoop);
  50. }
  51. gameLoop();
复制代码

实战案例

下面是一个综合应用前面所学知识的实战案例:一个简单的打砖块游戏。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Breakout Game</title>
  7.     <style>
  8.         body {
  9.             margin: 0;
  10.             padding: 0;
  11.             display: flex;
  12.             justify-content: center;
  13.             align-items: center;
  14.             height: 100vh;
  15.             background-color: #f0f0f0;
  16.         }
  17.         canvas {
  18.             border: 1px solid #333;
  19.             background-color: #fff;
  20.         }
  21.     </style>
  22. </head>
  23. <body>
  24.     <canvas id="gameCanvas" width="800" height="600"></canvas>
  25.     <script>
  26.         const canvas = document.getElementById('gameCanvas');
  27.         const ctx = canvas.getContext('2d');
  28.         // 游戏状态
  29.         let gameRunning = true;
  30.         let score = 0;
  31.         let lives = 3;
  32.         // 球
  33.         const ball = {
  34.             x: canvas.width / 2,
  35.             y: canvas.height - 100,
  36.             radius: 10,
  37.             dx: 4,
  38.             dy: -4,
  39.             speed: 4
  40.         };
  41.         // 挡板
  42.         const paddle = {
  43.             width: 100,
  44.             height: 15,
  45.             x: (canvas.width - 100) / 2,
  46.             y: canvas.height - 30,
  47.             speed: 8,
  48.             dx: 0
  49.         };
  50.         // 砖块
  51.         const brickRowCount = 5;
  52.         const brickColumnCount = 9;
  53.         const brickWidth = 75;
  54.         const brickHeight = 20;
  55.         const brickPadding = 10;
  56.         const brickOffsetTop = 60;
  57.         const brickOffsetLeft = 35;
  58.         const bricks = [];
  59.         // 初始化砖块
  60.         function initBricks() {
  61.             for (let c = 0; c < brickColumnCount; c++) {
  62.                 bricks[c] = [];
  63.                 for (let r = 0; r < brickRowCount; r++) {
  64.                     bricks[c][r] = {
  65.                         x: 0,
  66.                         y: 0,
  67.                         status: 1,
  68.                         color: `hsl(${r * 40}, 70%, 50%)`
  69.                     };
  70.                 }
  71.             }
  72.         }
  73.         // 键盘控制
  74.         let rightPressed = false;
  75.         let leftPressed = false;
  76.         document.addEventListener('keydown', keyDownHandler);
  77.         document.addEventListener('keyup', keyUpHandler);
  78.         document.addEventListener('mousemove', mouseMoveHandler);
  79.         function keyDownHandler(e) {
  80.             if (e.key === 'Right' || e.key === 'ArrowRight') {
  81.                 rightPressed = true;
  82.             } else if (e.key === 'Left' || e.key === 'ArrowLeft') {
  83.                 leftPressed = true;
  84.             }
  85.         }
  86.         function keyUpHandler(e) {
  87.             if (e.key === 'Right' || e.key === 'ArrowRight') {
  88.                 rightPressed = false;
  89.             } else if (e.key === 'Left' || e.key === 'ArrowLeft') {
  90.                 leftPressed = false;
  91.             }
  92.         }
  93.         function mouseMoveHandler(e) {
  94.             const relativeX = e.clientX - canvas.offsetLeft;
  95.             if (relativeX > 0 && relativeX < canvas.width) {
  96.                 paddle.x = relativeX - paddle.width / 2;
  97.             }
  98.         }
  99.         // 碰撞检测
  100.         function collisionDetection() {
  101.             for (let c = 0; c < brickColumnCount; c++) {
  102.                 for (let r = 0; r < brickRowCount; r++) {
  103.                     const b = bricks[c][r];
  104.                     if (b.status === 1) {
  105.                         if (ball.x > b.x &&
  106.                             ball.x < b.x + brickWidth &&
  107.                             ball.y > b.y &&
  108.                             ball.y < b.y + brickHeight) {
  109.                             ball.dy = -ball.dy;
  110.                             b.status = 0;
  111.                             score++;
  112.                            
  113.                             // 检查是否所有砖块都被击破
  114.                             if (score === brickRowCount * brickColumnCount) {
  115.                                 alert('恭喜你,赢了!');
  116.                                 document.location.reload();
  117.                             }
  118.                         }
  119.                     }
  120.                 }
  121.             }
  122.         }
  123.         // 绘制球
  124.         function drawBall() {
  125.             ctx.beginPath();
  126.             ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
  127.             ctx.fillStyle = '#0095DD';
  128.             ctx.fill();
  129.             ctx.closePath();
  130.         }
  131.         // 绘制挡板
  132.         function drawPaddle() {
  133.             ctx.beginPath();
  134.             ctx.rect(paddle.x, paddle.y, paddle.width, paddle.height);
  135.             ctx.fillStyle = '#0095DD';
  136.             ctx.fill();
  137.             ctx.closePath();
  138.         }
  139.         // 绘制砖块
  140.         function drawBricks() {
  141.             for (let c = 0; c < brickColumnCount; c++) {
  142.                 for (let r = 0; r < brickRowCount; r++) {
  143.                     if (bricks[c][r].status === 1) {
  144.                         const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
  145.                         const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
  146.                         bricks[c][r].x = brickX;
  147.                         bricks[c][r].y = brickY;
  148.                         ctx.beginPath();
  149.                         ctx.rect(brickX, brickY, brickWidth, brickHeight);
  150.                         ctx.fillStyle = bricks[c][r].color;
  151.                         ctx.fill();
  152.                         ctx.closePath();
  153.                     }
  154.                 }
  155.             }
  156.         }
  157.         // 绘制分数
  158.         function drawScore() {
  159.             ctx.font = '16px Arial';
  160.             ctx.fillStyle = '#0095DD';
  161.             ctx.fillText('分数: ' + score, 8, 20);
  162.         }
  163.         // 绘制生命值
  164.         function drawLives() {
  165.             ctx.font = '16px Arial';
  166.             ctx.fillStyle = '#0095DD';
  167.             ctx.fillText('生命: ' + lives, canvas.width - 65, 20);
  168.         }
  169.         // 更新游戏状态
  170.         function update() {
  171.             if (!gameRunning) return;
  172.             // 移动挡板
  173.             if (rightPressed && paddle.x < canvas.width - paddle.width) {
  174.                 paddle.x += paddle.speed;
  175.             } else if (leftPressed && paddle.x > 0) {
  176.                 paddle.x -= paddle.speed;
  177.             }
  178.             // 移动球
  179.             ball.x += ball.dx;
  180.             ball.y += ball.dy;
  181.             // 球碰到左右墙壁
  182.             if (ball.x + ball.dx > canvas.width - ball.radius || ball.x + ball.dx < ball.radius) {
  183.                 ball.dx = -ball.dx;
  184.             }
  185.             // 球碰到上墙壁
  186.             if (ball.y + ball.dy < ball.radius) {
  187.                 ball.dy = -ball.dy;
  188.             }
  189.             // 球碰到挡板
  190.             else if (ball.y + ball.dy > paddle.y - ball.radius &&
  191.                      ball.x > paddle.x &&
  192.                      ball.x < paddle.x + paddle.width) {
  193.                 // 根据球击中挡板的位置改变反弹角度
  194.                 const hitPos = (ball.x - paddle.x) / paddle.width;
  195.                 ball.dx = 8 * (hitPos - 0.5);
  196.                 ball.dy = -ball.dy;
  197.             }
  198.             // 球碰到底部
  199.             else if (ball.y + ball.dy > canvas.height - ball.radius) {
  200.                 lives--;
  201.                 if (!lives) {
  202.                     alert('游戏结束');
  203.                     document.location.reload();
  204.                 } else {
  205.                     ball.x = canvas.width / 2;
  206.                     ball.y = canvas.height - 100;
  207.                     ball.dx = 4;
  208.                     ball.dy = -4;
  209.                     paddle.x = (canvas.width - paddle.width) / 2;
  210.                 }
  211.             }
  212.             // 碰撞检测
  213.             collisionDetection();
  214.         }
  215.         // 绘制游戏
  216.         function draw() {
  217.             ctx.clearRect(0, 0, canvas.width, canvas.height);
  218.             drawBricks();
  219.             drawBall();
  220.             drawPaddle();
  221.             drawScore();
  222.             drawLives();
  223.         }
  224.         // 游戏循环
  225.         function gameLoop() {
  226.             update();
  227.             draw();
  228.             requestAnimationFrame(gameLoop);
  229.         }
  230.         // 初始化游戏
  231.         initBricks();
  232.         gameLoop();
  233.     </script>
  234. </body>
  235. </html>
复制代码

这个打砖块游戏综合运用了Canvas的多种技术:

• 基本图形绘制(球、挡板、砖块)
• 颜色和样式(填充颜色)
• 文本绘制(分数和生命值)
• 动画(使用requestAnimationFrame)
• 碰撞检测
• 用户输入处理(键盘和鼠标)
• 游戏状态管理

总结与展望

HTML5 Canvas是一个强大的网页图形绘制技术,它提供了丰富的API用于创建各种图形、动画和交互式应用。从基本的图形绘制到复杂的动画效果,Canvas都能胜任。

随着Web技术的发展,Canvas的应用场景越来越广泛,包括:

• 数据可视化:图表、地图等
• 游戏:2D游戏、简单的3D游戏
• 图像处理:滤镜、特效等
• 创意编程:生成艺术、交互式艺术等

未来,随着WebGL和WebGPU等技术的发展,网页图形处理能力将进一步提升,Canvas也将继续演进,为开发者提供更强大的工具和更好的性能。

掌握Canvas技术,不仅能让你创建出丰富多彩的网页应用,还能为你打开一扇通往图形编程和游戏开发的大门。希望这篇文章能帮助你全面了解和掌握HTML5 Canvas的核心技术,从基础到高级应用,成为一名优秀的网页图形开发者。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则