从零打造 Canvas 飞机游戏与 ECharts 可视化:HTML5 前端双实战

2 阅读3分钟

从零打造 Canvas 飞机游戏与 ECharts 可视化:HTML5 前端双实战

小时候偷偷在电脑课上玩 Flash 小游戏,现在我用 300 行代码复刻了那个味道——还顺手搭了个销售数据大屏。

一、为什么选这两个项目?

HTML5 时代,Canvas 和 ECharts 是前端工程师的两把利器:

  • Canvas:底层绘制 API,游戏、动画、可视化图表的基石
  • ECharts:基于 Canvas 封装的数据可视化库,开箱即用

本文带你从 Canvas 原生 API 到 ECharts 高级配置,完成两个完整实战项目。


二、Canvas 飞机射击游戏:300 行代码的复古浪漫

2.1 游戏架构一览

requestAnimationFrame 主循环
    ├── 输入处理(键盘事件)
    ├── 游戏逻辑更新(移动、碰撞、生成)
    └── 绘制渲染(星空背景、玩家、子弹、敌机)

2.2 核心机制拆解

帧动画:为什么不用 setInterval?
// ❌ 不推荐:setInterval 与屏幕刷新率不同步
setInterval(() => {
  update(); draw();
}, 16); // 约60fps,但实际可能撕裂

// ✅ 推荐:requestAnimationFrame 自动适配刷新率
function loop(timestamp) {
  update(timestamp);
  draw();
  requestAnimationFrame(loop); // 递归调用,与显示器同步
}
requestAnimationFrame(loop);

requestAnimationFrame 是浏览器提供的帧同步调度函数,它会自动匹配显示器的刷新率(通常是 60Hz 或 120Hz),避免画面撕裂和性能浪费。

碰撞检测:简单的矩形碰撞
function rectCollide(a, b) {
  return (
    Math.abs(a.x - b.x) < (a.w + b.w) / 2 &&
    Math.abs(a.y - b.y) < (a.h + b.h) / 2
  );
}

用中心点距离判断,比四个边界比较更简洁。虽然不够精确(飞机是三角形),但对街机游戏来说完全够用。

星空背景:伪随机滚动
for (let i = 0; i < 60; i++) {
  const sx = (i * 137 + 50) % canvas.width;
  const sy = (i * 211 + timestamp * 0.05) % canvas.height;
  ctx.fillStyle = `rgba(255,255,255,${0.3 + (i % 5) * 0.15})`;
  ctx.fillRect(sx, sy, 1.5, 1.5);
}

用固定乘数(137、211)生成伪随机坐标,配合 timestamp 实现滚动效果。不需要额外数组存储星星状态,纯函数式渲染,简洁优雅。

玩家飞机绘制:Canvas Path2D 基础
ctx.fillStyle = '#00d4ff';
ctx.beginPath();
ctx.moveTo(0, -player.h / 2);      // 机头顶点
ctx.lineTo(-player.w / 2, player.h / 2); // 左翼
ctx.lineTo(0, player.h / 4);       // 机尾凹槽
ctx.lineTo(player.w / 2, player.h / 2);  // 右翼
ctx.closePath();
ctx.fill();

beginPath + moveTo + lineTo 绘制三角形机身,加上圆形驾驶舱和三角形尾焰,一个像素风飞机就诞生了。

2.3 游戏状态管理

let score = 0;
let gameOver = false;
let bullets = [];  // 子弹池
let enemies = [];  // 敌机池

// 射击冷却机制
const SHOOT_COOLDOWN = 200; // 毫秒
let lastShootTime = 0;

if (keys[' '] && timestamp - lastShootTime >= SHOOT_COOLDOWN) {
  shoot();
  lastShootTime = timestamp;
}

用时间戳差值做冷却控制,比 setTimeout 更精确,也更适合帧循环架构。


三、ECharts 销售数据可视化:让数据开口说话

3.1 数据层:优雅的 Getter 设计

const salesData = {
  company: '肖氏电商集团',
  category: '运动鞋',
  unit: '百万元',
  year: 2025,
  monthly: [
    { month: '1月', sales: 18.7 },
    // ... 12个月数据
  ],

  get months() {
    return this.monthly.map(item => item.month);
  },

  get values() {
    return this.monthly.map(item => item.sales);
  }
};

getter 动态提取数组,数据结构和展示逻辑解耦。修改原始数据时,图表自动同步。

3.2 图表配置:渐变柱状图

const option = {
  title: {
    text: `${salesData.company}${salesData.category}销售`,
    subtext: `${salesData.year}年 月度销售额(${salesData.unit})`,
    left: 'center'
  },
  series: [{
    type: 'bar',
    data: salesData.values,
    itemStyle: {
      color: {
        type: 'linear',
        x: 0, y: 0, x2: 0, y2: 1,
        colorStops: [
          { offset: 0, color: '#667eea' },  // 顶部紫蓝
          { offset: 1, color: '#764ba2' }   // 底部深紫
        ]
      },
      borderRadius: [6, 6, 0, 0]  // 顶部圆角
    },
    emphasis: {
      itemStyle: {
        color: {
          type: 'linear',
          colorStops: [
            { offset: 0, color: '#f093fb' }, // hover 变粉红
            { offset: 1, color: '#f5576c' }
          ]
        }
      }
    }
  }]
};

渐变配色 + 圆角柱顶 + hover 高亮,三行配置让图表从"能看"变成"好看"。

3.3 响应式适配

window.addEventListener('resize', () => {
  myChart.resize();
});

ECharts 内置 resize 方法,窗口变化时自动重绘,大屏展示必备。


四、两个项目的对比与思考

维度Canvas 飞机游戏ECharts 数据可视化
抽象层级底层 API,逐像素控制高层封装,声明式配置
开发效率低(需手写绘制逻辑)高(配置即图表)
灵活性极高(任意图形、动画)中(受限于内置类型)
适用场景游戏、自定义动画报表、Dashboard、大屏
性能优化手动管理(对象池、脏矩形)自动优化(Canvas 底层)

核心洞察:ECharts 本质上是 Canvas 的高级封装。理解 Canvas 原理,才能用好 ECharts;会用 ECharts,也要知道底层发生了什么。


五、工程化:Vite 脚手架

两个项目都用 Vite 初始化:

{
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "echarts": "^6.1.0"
  }
}
  • type: "module" 原生 ESM,无需 Babel
  • Vite 开发服务器秒级冷启动
  • 生产构建自动 Tree Shaking

六、写在最后

Canvas 是前端的"画笔",ECharts 是"印刷机"。一个让你自由创作,一个让你高效交付。

小时候玩游戏,现在写游戏——技术让热爱有了形状。

完整代码已开源,欢迎 Star 和 PR。


本文代码基于 Vite + ECharts 6.x + 原生 Canvas API,浏览器兼容性:Chrome 80+、Firefox 75+、Edge 80+。