HTML5 Canvas 从入门到实战:画布绘图 · 帧动画 · 小游戏 · 数据可视化

发布时间:2026/6/16 0:39:12
HTML5 Canvas 从入门到实战:画布绘图 · 帧动画 · 小游戏 · 数据可视化 HTML5 Canvas 是前端领域一块真正的画布——它赋予开发者在浏览器中任意绘制的能力。无论是 2D 游戏、数据可视化图表还是酷炫的交互动效Canvas 都是核心基石。本文将从最基础的 API 出发逐步深入到帧动画、完整小游戏开发最后结合 ECharts 完成数据可视化报表带你系统掌握 Canvas 技术栈。一、 认识 Canvas浏览器里的画布1.1canvas标签Canvas 的一切从一个 HTML 标签开始canvasidmyCanvaswidth600height400styleborder:1px solid #333;您的浏览器不支持 canvas旧 IE 会显示这段文字/canvas 几个关键点width和height是 Canvas 的像素尺寸默认为 300×150不要用 CSS 来设置——CSS 宽高只会拉伸画布而不会改变分辨率。标签内的文字是降级内容当浏览器不支持 Canvas 时才会显示。对于极老旧的浏览器可以使用 polyfill 库来做兼容。1.2 获取绘制上下文Canvas 本身只是一个容器真正绘图靠的是绘制上下文Contextconstcanvasdocument.querySelector(#myCanvas);constctxcanvas.getContext(2d);// 2D 绘制上下文// canvas.getContext(webgl) // 3D 绘制上下文激发 GPU拿到ctx之后你就拥有了一套完整的 JS 绘图 API可以随心所欲地绘制任何图形。1.3 Canvas 坐标系Canvas 坐标系以左上角为原点 (0, 0)x 轴向右延伸y 轴向下延伸。理解这一点对后续所有绘制都至关重要。(0,0) ──────────────────────────────── x 轴向右为正 │ │ ┌──────────────────────┐ │ │ │ │ │ Canvas 绘制区域 │ width 600 │ │ │ │ │ · (x, y) │ │ │ │ │ └──────────────────────┘ │ y 轴向下为正 height 400记忆技巧Canvas 的坐标原点在左上角这和 CSS 的盒模型方向一致但和数学中的笛卡尔坐标系左下角为原点y 轴向上不同。1.4 ️ 基础绘制 API下面这段代码涵盖了最核心的几个 API——绘制矩形、描边矩形、清除区域、设置颜色constcanvasdocument.querySelector(#myCanvas);constctxcanvas.getContext(2d);// 设置填充颜色绘制一个实心矩形ctx.fillStyle#4299e1;ctx.fillRect(20,20,100,80);// fillRect(x, y, width, height)// 设置描边颜色与线宽绘制一个空心矩形ctx.strokeStyle#f56565;ctx.lineWidth4;ctx.strokeRect(150,20,100,80);// 清除指定矩形区域变成透明ctx.clearRect(50,50,40,30);// clearRect(x, y, width, height)API作用fillRect(x, y, w, h)绘制实心矩形strokeRect(x, y, w, h)绘制空心矩形描边clearRect(x, y, w, h)清除指定区域fillStyle填充颜色strokeStyle边框描边颜色lineWidth线条宽度1.5 更多常用 API除了矩形Canvas 还提供了丰富的 2D 绘图 API核心模式始终不变先设置样式 → 再调用绘制方法。// 路径与形状 ctx.beginPath();// 开始新路径ctx.moveTo(50,50);// 移动画笔ctx.lineTo(150,50);// 画直线ctx.arc(100,75,50,0,Math.PI*2);// 画圆弧圆心x, 圆心y, 半径, 起始角, 结束角ctx.quadraticCurveTo(280,150,350,250);// 二次贝塞尔曲线ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y);// 三次贝塞尔曲线// 文字渲染 ctx.fontbold 28px Microsoft YaHei, sans-serif;ctx.fillText(Hello Canvas!,50,100);// 实心文字ctx.strokeText(Hello Canvas!,50,160);// 空心文字// 图片绘制 ctx.drawImage(image,0,0);// 原尺寸绘制ctx.drawImage(image,0,0,100,80);// 指定宽高ctx.drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh);// 裁剪绘制精灵图// 渐变 constgradctx.createLinearGradient(0,0,200,0);// 线性渐变grad.addColorStop(0,#ff0000);grad.addColorStop(1,#0000ff);ctx.fillStylegrad;constrGradctx.createRadialGradient(x,y,r1,x,y,r2);// 径向渐变// 变换与状态 ctx.save();// 保存当前绘图状态ctx.translate(x,y);// 平移坐标系ctx.rotate(angle);// 旋转弧度ctx.scale(sx,sy);// 缩放ctx.restore();// 恢复到上次 save 的状态ctx.setTransform(a,b,c,d,e,f);// 直接设置变换矩阵// 合成与透明度 ctx.globalAlpha0.5;// 全局透明度ctx.globalCompositeOperationlighter;// 混合模式屏幕叠加发光ctx.globalCompositeOperationsource-over;// 恢复默认混合⚠️关键提醒每次绘制新形状前记得调用beginPath()否则新路径会叠加在旧路径上导致意外样式覆盖。每次变换操作前调用save()绘制完后调用restore()避免变换状态污染后续绘制。二、️ 让画面动起来帧动画与 requestAnimationFrame2.1 为什么不用 setInterval很多人一开始会用setInterval来做动画// ❌ 不推荐setInterval((){// 更新 绘制},16);// 约 60fps但这有两个致命问题时机不同步setInterval的回调时机与显示器刷新率没有关联可能出现画面撕裂。后台继续执行即使页面切到后台标签页定时器依然在跑浪费 CPU。2.2 ✔️ requestAnimationFrame —— 与屏幕刷帧率同步requestAnimationFrame由浏览器调度在每次屏幕刷新前执行回调天然匹配显示器的刷新率通常 60Hz。页面不可见时自动暂停。下面是一个简单的水平移动动画constcanvasdocument.querySelector(#myCanvas);constctxcanvas.getContext(2d);letx20;constspeed3;functionanimate(){// 第一步清空整个画布ctx.clearRect(0,0,canvas.width,canvas.height);// 第二步在新位置绘制矩形ctx.fillStyle#4299e1;ctx.fillRect(x,20,100,80);// 第三步更新位置xspeed;if(xcanvas.width)x-100;// 递归请求下一帧requestAnimationFrame(animate);}animate();// ▶️ 启动动画循环这就是帧动画的核心三步骤┌──────────┐ ┌──────────┐ ┌──────────┐ │ clear │ → │ draw │ → │ update │ → 下一帧... └──────────┘ └──────────┘ └──────────┘三、 进阶实战雷霆战机 —— Canvas 射击游戏有了前面的基础我们来看一个完整的实战项目——“雷霆战机”一款由原生 Canvas 驱动的 2D 竖版射击小游戏。3.1 工程化搭建项目使用Vite构建模块化拆分airplane/ ├── index.html # 入口页面 ├── package.json # 依赖配置 └── src/ ├── main.js # ️ 游戏主逻辑状态机、输入、碰撞、游戏循环 ├── render.js # ️ 渲染模块全部 Canvas 绘制函数 ├── audio.js # 音效模块Web Audio API └── style.css # 全屏布局、星空背景动画3.2 核心 Canvas 技巧逻辑坐标系与自适应游戏定义了一个逻辑坐标系固定高度 750px通过setTransform统一缩放到任意屏幕constBASE_H750;letGAME_W,GAME_H,viewScale;functionresize(){constdprMath.min(window.devicePixelRatio||1,2);canvas.widthMath.floor(window.innerWidth*dpr);canvas.heightMath.floor(window.innerHeight*dpr);viewScalecanvas.height/BASE_H;// 缩放因子GAME_HBASE_H;GAME_WMath.ceil(canvas.width/viewScale);}// 渲染时一行代码完成所有坐标映射ctx.setTransform(viewScale,0,0,viewScale,sx,sy);setTransform后面跟的sx, sy是屏幕震动偏移量——被击中时加入随机抖动产生受击反馈。3.3 游戏循环与状态机游戏分为 MENU → PLAYING → GAMEOVER 三个状态每帧执行functiongameLoop(){update();// 更新输入 → 移动 → 碰撞 → 粒子衰减render();// ️ 渲染背景 → 对象 → 特效 → HUDrequestAnimationFrame(gameLoop);}3.4 Canvas 分层渲染渲染采用从背景到前景的分层策略每一层用到不同的 Canvas APIfunctionrender(){ctx.setTransform(viewScale,0,0,viewScale,sx,sy);// 第 1 层深空背景ctx.fillStyle#0a0a1e;ctx.fillRect(0,0,GAME_W,GAME_H);// 第 2 层星云径向渐变 createRadialGradientdrawNebula(ctx,nebulaBlobs);// 第 3 层双层星空视差远星慢、近星快drawStars(ctx,farStars);drawStars(ctx,nearStars);// 第 4 层游戏对象战机、敌机、子弹drawPlayer(ctx,player,frameCount,invincibleTimer);for(consteofenemies)drawEnemy(ctx,e);for(constbofbullets)drawBullet(ctx,b);// 第 5 层爆炸粒子 Bloom 发光drawParticles(ctx,particles);drawBloomPass(ctx,player,particles,GAME_W,GAME_H);// 第 6 层HUD分数、生命、连击 虚拟摇杆drawHUD(ctx,score,highScore,lives,combo,...);drawVirtualControls(ctx,joystick,fireBtn,GAME_W,GAME_H);}战机完全用 Canvas API 手绘不依赖任何图片资源——从能量护盾光晕createRadialGradient到金属机身渐变createLinearGradient再到翼刃发光shadowBlurbezierCurveTo总计十余层细节层层叠加。3.5 碰撞检测与视觉反馈碰撞检测使用 AABB轴对齐包围盒算法本质是坐标值的纯数学运算functionhitTest(a,b){return(a.x-a.w/2b.xb.w/2a.xa.w/2b.x-b.w/2a.y-a.h/2b.yb.h/2a.ya.h/2b.y-b.h/2);}击中敌机后Canvas 层面的反馈层层递进 反馈️ Canvas 实现 爆炸粒子四类粒子内环慢速、外环爆发、碎片、火花环每帧位置 速度life衰减至 0 后移除 浮动分数fillText绘制文字globalAlpha渐隐字号随life动态缩小 屏幕震动setTransform末尾两个参数加入随机偏移shakeIntensity每帧衰减 Bloom 发光globalCompositeOperation lighter累加高亮像素3.6 ✨ 游戏亮点一览⚡ 功能 技术实现 多端适配键盘方向键/WASD 移动端虚拟摇杆 Pointer Events 音效Web Audio API 振荡器合成无需加载音频文件 存档localStorage持久化最高分✨ 无敌闪烁globalAlpha周期性切换四、 数据可视化ECharts 快速上手 —— Canvas 的另一种打开方式Canvas 的另一个重要应用领域是数据可视化。ECharts 是百度开源的高性能图表库底层基于 Canvas渲染封装了数十种常用图表类型。它的存在证明了 Canvas 不仅能做游戏在数据密集型场景同样强大。4.1 三步上手npminstallechartsECharts 的使用模式简洁到极致——初始化实例 → 配置 option → setOption 渲染import*asechartsfromecharts;constmyChartecharts.init(document.getElementById(chart));myChart.setOption({title:{text:赖氏电商 — 2025年运动鞋月度销售},tooltip:{trigger:axis},xAxis:{type:category,data:[1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月]},yAxis:{type:value},series:[{name:销售额,type:bar,data:[2.83,1.96,3.45,3.78,4.12,3.89,4.56,4.91,4.27,3.64,5.18,6.35],itemStyle:{color:newecharts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:#83bff6},{offset:1,color:#188df0},]),},}],});window.addEventListener(resize,()myChart.resize());4.2 从 Canvas 视角看 ECharts从 Canvas 开发者的角度ECharts 本质上是把下面这些手工操作封装成了声明式配置️ 手工 Canvas⚡ ECharts 替你做的事fillRect逐个画柱体根据series.data自动计算柱宽、间距、位置createLinearGradient手动构造渐变itemStyle.color配一个渐变对象即可fillText画坐标轴标签xAxis.data/yAxis自动布局鼠标坐标换算 碰撞检测tooltip开箱即用监听resize 重绘myChart.resize()一行搞定requestAnimationFrame动画插值内置平滑过渡动画手动计算图例位置和水晶球交互legend/dataZoom声明即用一句话理解ECharts 就像 Canvas 的高级封装层——你描述想要什么它帮你在 Canvas 上画出来。省掉了所有坐标计算、重绘调度和交互细节。五、 总结与思考学习路径回顾 阶段 内容 核心收获① Canvas 基础标签、坐标系、fillRect/arc/drawImage等核心 API理解画布与绘制上下文② 帧动画requestAnimationFrameclear → draw → update掌握动画循环机制③ 游戏实战雷霆战机状态机、碰撞、粒子、音效模块化架构、Canvas 分层渲染、视觉特效④ 数据可视化ECharts Vite 工程化开发声明式图表配置、Canvas 的工程化封装Canvas vs DOM vs SVG特性 Canvas DOM✒️ SVG渲染方式像素级位图元素树矢量图形大量对象性能⭐⭐⭐ 优秀⭐ 较差⭐⭐ 中等交互能力需手动实现⭐⭐⭐ 天然支持⭐⭐⭐ 天然支持适用场景游戏、粒子、图像处理常规 UI 界面图标、可缩放图形 技术展望随着 AI 时代的到来Canvas 正在迎来新的爆发点 AI 游戏结合物理大模型Canvas 作为渲染层生成实时画面 WebGL / Three.jsgetContext(webgl)打开三维世界大门浏览器 3D 游戏与元宇宙成为可能️ 生成式艺术AI 模型输出绘图指令Canvas 实时呈现一句话总结Canvas 是一张白纸JS 是你的画笔requestAnimationFrame是时间的节拍器——三者合一就能在浏览器里创造出无限可能。