项目:html5-games | 版本:v1.0 | 生成日期:2026-10-06 | 依据:源码分析
H5 游戏厅是一个纯前端的 HTML5 小游戏合集站点,包含 79 款可直接游玩的小游戏。根据 package.json 的 description 字段("H5 游戏厅 — 79 款可游玩的 HTML5 小游戏合集(响应式 + 触屏适配)")与 README.md 的自述,该项目从同组织下的 html5-examples 平台「游戏演示」模块独立拆分而来,拆分的核心动机是解除 PC-only 限制——即在保持桌面体验的同时,完整支持响应式布局与移动端触屏操作。
从 src/config/games.ts 文件头的演进注释可以还原项目历史:最初由原「视觉演示」(ui_visual_demos 表 game 引擎)改造而来,剔除 3D 与 CSS 动画后保留 15 款可玩小游戏,随后经历「主题化改造 + 经典示例填充」(23 款)→「GitHub 框架示例集成」(43 款)→「经典游戏精选集成」(63 款)→「第三批精选集成」(80 条配置、79 个实现文件,详见第 4 节统计口径说明)五个阶段,最终形成当前规模。docs/plans/ 与 docs/research/ 目录保留了各批次的实施计划与调研记录(如 docs/research/framework-research-summary.md 记录了从 8 个框架 36 个调研项目中精选玩法重写的决策过程),可作为历史背景阅读,但事实以源码为准。
项目的目标用户与使用场景:
项目的三条核心设计原则(源自 README「要点」与源码注释,均已在源码中验证):
src/config/games.ts),进度/最高分持久化走 localStorage(src/utils/highScore.ts),构建后为纯静态站点。这条原则的直接收益是运维成本归零:没有数据库迁移、没有接口版本兼容、没有服务端扩容;代价是没有跨设备同步与排行榜等社交能力——这是产品层面的有意取舍,接手者若要引入后端,应先评估它对「纯静态可部署」这一卖点的冲击。import.meta.glob(src/games/registry.ts)在构建期完成代码分割,每款游戏一个独立懒加载 chunk;详情页不使用运行时 SFC 编译器,首屏完全不包含任何游戏代码。这条原则同时解决了「首屏体积」与「安全性」两个问题——产物里不存在 vue/compiler-sfc,也意味着任何游戏改动都必须走完整的构建发布流程,无法热插拔。GameCover + src/components/games/gameCoverRender.ts 程序化绘制(暗色基底 + 分类色辉光 + 由 id 哈希决定的几何符号),音效与背景音由 src/composables/useGameAudio.ts / useGameBgm.ts 用 WebAudio 振荡器实时合成,全项目仅 public/media/mov_bbb.mp4 一个视频素材(供「视频拼图」游戏使用)。零素材让仓库体积极小、无版权风险,也让「新增一款游戏」不需要美工与音频资源——但代价是所有视觉个性只能来自程序化生成参数,想要精细的美术表现时这条路需要重新评估。从玩家视角走一遍典型旅程可以快速建立体感:打开站点落在 /games 大厅,首屏是欢迎语、主搜索框与四款「核心精选」横带;通过分类 chips(全部 79 / 益智 20 / 棋牌 7 / 动作 25 / 休闲 27)或搜索缩小范围,每页 24 款、URL 可分享;悬停卡片即后台预取游戏代码,点击进入整屏运行页,游戏在统一外壳(GameShell)中呈现——顶栏是标题与分数 HUD,右侧是玩法说明,舞台中央按设定的宽高比最大化;玩毕返回大厅,滚动位置与筛选条件原样保留,最高分已写入本机。整个过程没有一次网络请求指向后端,所有动态性都来自浏览器本地能力。
src/config/games.ts 的 localGameCategories)分类元数据同时定义了侧边栏展示顺序、material-symbols 图标与分类主题色(用于卡片 hover 边框、GameShell 强调色、程序化封面辉光)。以下数量由源码逐条统计得出:
| 分类 id | 名称 | 图标 | 主题色 | 数量 | 代表游戏 |
|---|---|---|---|---|---|
puzzle | 益智 | ms:extension | #8b5cf6 | 20 | 俄罗斯方块 · 2048 · 数独 · 扫雷 · 推箱子 · 接水管 · 华容道 · 数织 |
board | 棋牌 | ms:grid_on | #f59e0b | 7 | 五子棋(人机+双人) · 黑白棋 · 井字棋 · 数独 · 六边形策略 · 回合制战棋 · 小队 RPG |
action | 动作 | ms:sports_esports | #f43f5e | 25 | 飞机大战 · 坦克大战 · 吃豆人 · 超级马里奥 · 射线 FPS · 是男人就下 100 层 · 忍者跳跃 |
casual | 休闲 | ms:toys | #14b8a6 | 27 | 贪吃蛇 · 打砖块 · 合成大西瓜 · 泡泡龙 · 跳一跳 · 粒子烟花 · 黄金矿工 |
合计 79 款。一个值得注意的源码细节:casual 分类的注释明确记载「不可用 ms:catching_pokemon——该连字在 material-symbols@0.45.8 字体中缺失,会以字面文本渲染并撑破侧栏(已实测),此处选用语义相近的 toys」,这体现了项目「图标字体自托管 + 逐字符实测」的严谨态度。
分类归属说明:数独、塔防、迷宫生存等被归入非直觉分类(如塔防归 puzzle、数独归 board),这是配置中的既定事实,维护新游戏时应保持「category 只能取四者之一、以玩法主基调归类」的惯例。
LocalGame.group 字段)| 分组 | 数量 | 说明 |
|---|---|---|
| GitHub 示例集成 | 61 | 从开源示例精选玩法、原生 Canvas/DOM 重写 |
| 核心精选 | 7 | 首页「核心精选」横带来源(HallHero.vue 取该组前 4 款展示) |
| PixiJS 精选 | 3 | pixi-fireworks / pixi-pong / pixi-invaders,PixiJS 8 渲染 |
| 专题灵感 | 4 | 2048 · 看你有多色 · 是男人就下 100 层 · 坦克大战(id 以 special- 开头) |
| Canvas Test 整理 | 4 | 接水管 · 飞机大战 · 无尽跑酷 · 花式桌球 |
.vue 文件逐个扫描判定)| 渲染范式 | 数量 | 判定依据 | 代表 |
|---|---|---|---|
| 原生 Canvas 2D | 72 | 模板含 <canvas> | snake、tetris、pacman、gobang、wordle 等 |
| DOM/CSS(Vue 响应式渲染) | 4 | 无 canvas,纯模板+样式 | connect-pipe、memory-match、special-2048、special-look-def-color |
| PixiJS 8(WebGL) | 3 | import { Application } from 'pixi.js' | pixi-fireworks、pixi-pong、pixi-invaders |
为避免对 79 款游戏逐一展开,文档集按「玩法范式 + 技术实现」归纳为八类,每类深拆 1-2 款代表:
| 玩法范式 | 数量(约) | 代表深拆 | 归属篇章 |
|---|---|---|---|
| 网格益智(滑块/消除/填数/拼图) | ~20 | 2048(DOM 范式)、俄罗斯方块 | 02 / 05 |
| 棋牌对弈(含启发式 AI) | 7 | 五子棋 gobang | 02 |
| 街机动作(贪吃蛇/吃豆人/平台跳跃) | ~15 | 贪吃蛇 snake | 02 / 05 |
| 弹幕射击(纵向卷轴/光枪/射线 FPS) | ~8 | 飞机大战、射线 FPS | 02 |
| 物理弹射(桌球/弹珠/合成大西瓜/愤怒的小鸟) | ~10 | 弹球对战 pixi-pong | 02 |
| 节奏反应(音游/打地鼠/别踩白块/西蒙) | ~7 | 节奏音游 rhythm | 02 |
| 塔防策略(经典塔防/放置/主题塔防) | 3 | 塔防 tower-defense | 02 |
| 文字限时休闲(Wordle/猜数字/打字/垃圾分类) | ~8 | 猜单词 wordle | 02 |
以下信息均来自 package.json、vite.config.ts、vitest.config.ts 与源码实际用法:
| 技术 | 版本 | 用途 | 选型理由(源码/配置佐证) |
|---|---|---|---|
| Vue | ^3.5.0 | UI 框架(Composition API + <script setup>) | 组件化承载 79 个自包含游戏单元;defineAsyncComponent 天然衔接懒加载 chunk |
| Vite | ^5.0.0 | 构建工具 | import.meta.glob 构建期代码分割是游戏加载框架的基石(src/games/registry.ts) |
| TypeScript | ^5.0.0(strict) | 框架层类型安全 | tsconfig.json 开启 strict;注意:79 款游戏中仅 5 款使用 lang="ts"(连连看/华容道/数独/消消乐/推箱子),其余为 JS SFC,属于「框架强类型、游戏弱类型」的分层策略 |
| vue-router | ^4.3.0 | SPA 路由 | 仅两条业务路由(/games、/game/:id),createWebHistory + 深链参数 |
| Tailwind CSS | ^4.0.0(@tailwindcss/vite) | 原子化 CSS | v4 的 @theme inline 直接桥接 CSS 变量设计令牌(src/assets/main.css) |
| @vueuse/core | ^14.3.0 | 组合式工具 | useListFilter.ts 依赖 watchDebounced 实现搜索防抖 |
| pixi.js | ^8.19.0 | WebGL 渲染 | 仅 3 款游戏使用;vite.config.ts 的 manualChunks 将其独立为 pixi-vendor chunk 按需加载,避免污染首屏 |
| material-symbols | ^0.45.8 | 图标字体(自托管 npm 包) | main.ts 引入 material-symbols/outlined.css,无在线图标 API 依赖 |
| Vitest | ^2.1.9 | 单元测试 | vitest.config.ts:node 环境、只跑 src/**/__tests__/**/*.test.ts |
| ESLint 10 + eslint-plugin-vue + typescript-eslint + prettier | — | 代码质量 | eslint.config.js 平面配置;README 声称「零错误」为 CI 门禁 |
| vue-tsc | ^3.3.8 | 类型检查 | pnpm typecheck = vue-tsc --noEmit |
| pnpm 10.23 + Node ≥22 | — | 包管理与运行时 | package.json 的 packageManager 与 engines 字段钉版 |
明确不引入的东西(同样是设计决策):无 Pinia/Vuex 状态库(状态管理靠模块级单例 ref + props/slots,见 02-页面设计.md 第 5 节)、无后端/BFF、无图片与音频素材文件、无 UI 组件库(AppButton/AppInput 等均为自研薄封装)。
html5-games/
├── index.html # 入口 HTML:内联「启动屏」(#boot-splash)、Umami 统计、Google Fonts 懒加载
├── package.json # 脚本:dev(5556)/build/preview/test/lint/typecheck
├── vite.config.ts # @ 别名→src、manualChunks(vendor/vueuse-vendor/pixi-vendor)、chunk 命名
├── vitest.config.ts # node 环境,@ 别名,测试目录 src/**/__tests__/
├── tsconfig.json / env.d.ts # strict TS;.vue 模块声明
├── eslint.config.js # ESLint 平面配置(vue + ts + prettier 兼容)
├── .github/workflows/ # CI:install → typecheck → lint → test → build
├── public/
│ ├── favicon.svg
│ └── media/mov_bbb.mp4 # 「视频拼图」游戏唯一外部媒体素材
├── docs/ # 历史规划/调研/设计稿(plans/ research/ specs/),非运行时依赖
└── src/
├── main.ts # 入口:createApp → use(router) → mount('#app');引 main.css 与图标字体
├── App.vue # 根组件:AppHeader(route.meta.hideHeader 控制) + router-view + 移除启动屏
├── router/index.ts # 路由表 + scrollBehavior(大厅滚动恢复) + 标题守卫 + chunk 失败自愈
├── types/index.ts # ListFilterState(列表筛选共享类型)
├── config/
│ └── games.ts # ★ 游戏元数据唯一登记处:LocalGame[79] + 四分类元数据 + localGamesMap
├── games/
│ ├── registry.ts # ★ import.meta.glob 懒加载映射 + prefetchGame(hover 预取)
│ └── <id>.vue × 79 # 游戏组件:自包含(模板+玩法逻辑+样式),每款一个 chunk
├── pages/
│ ├── GamesPage.vue # 大厅:筛选管道 → 分页切片 → 网格渲染;URL 深链;键盘导航
│ └── GameDetailPage.vue # 运行页:localGamesMap 查元数据 → gameModules 取 loader → 异步挂载
├── components/
│ ├── ErrorFallback.vue # 加载/渲染失败兜底(重试按钮)
│ ├── games/
│ │ ├── GameShell.vue # ★ 游戏统一外壳:顶栏/HUD/控制键/舞台/info 栏/overlay 插槽
│ │ ├── GameCard.vue # 大厅卡片:真实 <a> 链接 + hover 预取 + 键盘选中态
│ │ ├── GameCover.vue # 封面 <img>:从渲染管线取 objectURL
│ │ ├── gameCoverRender.ts # ★ 封面渲染管线:OffscreenCanvas 绘制 → worker 编码 webp → 模块级缓存
│ │ ├── coverEncode.worker.ts# webp 编码 worker(主线程保护)
│ │ └── HallHero.vue # 首屏:欢迎语 + 主搜索 + 核心精选横带 + 随机开玩
│ ├── layout/
│ │ ├── AppHeader.vue # 64px 固定顶栏(Logo + 游戏总数)
│ │ ├── Pagination.vue # 分页器
│ │ └── SiteFooter.vue # 页脚
│ └── common/
│ ├── AppIcon.vue # material-symbols 图标封装
│ ├── AppButton.vue / AppInput.vue
│ └── CategoryChips.vue # 分类/排序 chip 组(受控 v-model)
├── composables/
│ ├── useGameAudio.ts # ★ WebAudio 音效:16 种合成音色 + 全局静音持久化 + 单例 AudioContext
│ ├── useGameBgm.ts # ★ 程序化背景音:种子确定性作曲(纯函数 createBgmPlan 可单测)
│ ├── useCanvas.ts # useCanvasStage 的别名封装(context/startAnimation/clearCanvas)
│ ├── useCanvasStage.ts # ★ canvas 舞台:DPR 适配 + ResizeObserver + rAF 循环 + 事件清理
│ ├── useAnimationLoop.ts # 无 canvas 游戏的 rAF 循环 + 事件注册清理
│ ├── useListFilter.ts # 列表筛选:实时/防抖双状态 + URL query 同步
│ ├── usePagination.ts # 分页状态:clamp、自动重置
│ └── useListShortcuts.ts # 大厅键盘导航:/ 聚焦搜索、j/k/↑/↓ 移动、Enter 打开
├── utils/
│ ├── highScore.ts # ★ 最高分读写(h5g:<id>-best 键,异常静默)
│ └── keyboard.ts # isTypingTarget(快捷键防误触)
└── assets/
└── main.css # 设计令牌:Tailwind v4 @theme + Linear 风格暗色 CSS 变量体系带 ★ 的文件是理解本项目的五个关键入口。统计口径说明:src/games/ 下共 80 个文件 = 79 个游戏 .vue + 1 个 registry.ts;src/config/games.ts 中 localGames 数组经测试(src/config/__tests__/games.test.ts)断言长度恰为 79,且与磁盘文件一一对应(「无缺文件/无孤儿文件」测试)。localGames 中有 80 条记录的说法仅见于文件头历史注释,以测试断言为准。
环境要求:Node.js ≥ 22、pnpm 10.23(corepack 可按 packageManager 字段自动取版)。
pnpm install # 安装依赖
pnpm dev # 启动开发服务器,http://localhost:5556(vite.config.ts 固定 host 127.0.0.1 / port 5556)
pnpm test # 运行 vitest 单元测试(node 环境)
pnpm lint # ESLint 检查
pnpm typecheck # vue-tsc 类型检查
pnpm build # 生产构建(输出 dist/,含每款游戏独立 chunk)
pnpm preview # 本地预览生产构建三条最常用的源码导航路径:
src/pages/GamesPage.vue(卡片点击 openGame)→ src/router/index.ts(/game/:id 路由)→ src/pages/GameDetailPage.vue(gameModules 动态挂载)→ src/games/snake.vue(任一游戏)。src/components/games/GameShell.vue——所有 79 款游戏的第一行模板都是 <GameShell>,标题/HUD/舞台/说明/浮层五个插槽决定了游戏的页面骨架。src/config/games.ts(游戏目录)+ src/utils/highScore.ts(h5g:<id>-best 键)+ src/composables/useGameAudio.ts 的 game-audio-muted 键与 useGameBgm.ts 的 h5g:bgm-muted 键。开发时的两个高频调试场景也在此提前给出答案。其一,为什么改了游戏但列表没变化:大厅数据来自 src/config/games.ts 的编译期常量,改完需要保存触发 Vite 热更新;若改的是测试断言相关文案(79 这个数字),记得同步 06 篇 SOP 列出的三处。其二,为什么声音不响:WebAudio 的 AudioContext 必须在用户手势内创建或恢复,直接刷新页面后程序化播放会被浏览器拦下——这是 useGameAudio.ts 里 unlock() 机制存在的原因,属预期行为而非缺陷;此外先检查 localStorage 里的 game-audio-muted 与 h5g:bgm-muted 两个键是否为 '1'。
| 篇章 | 内容 | 适合读者 |
|---|---|---|
| 00-项目总览(本篇) | 定位、分类统计、技术栈、目录导览 | 所有人 |
| 01-系统架构.md | 整体架构图、游戏加载机制、数据流、设计决策与模式 | 架构/维护者 |
| 02-页面设计.md | 路由页面总表、大厅与运行页交互、组件树、状态管理 | 前端开发 |
| 03-接口设计.md | 游戏注册配置格式、GameShell 契约、composables 与存储接口 | 游戏开发者 |
| 04-数据模型.md | LocalGame 元数据、localStorage 持久化结构、数据生命周期 | 全栈/维护者 |
| 05-核心流程.md | 端到端时序:游戏发现→加载→启动→结算、存档、触屏适配 | 维护者 |
| 06-部署运行.md | 环境要求、命令级操作、构建部署、测试门禁、新增游戏 SOP | 接手者必读 |
若你只有 30 分钟接手项目:读本篇第 4/5 节 → 01 篇第 2 节(游戏加载机制)→ 03 篇第 2 节(GameShell 契约)→ 06 篇第 5 节(新增游戏 SOP)。