用Claude生成单文件HTML赛博城市:停电自救全复盘
我最近做了一个挺有意思的实验让 Claude 生成一个单文件 HTML 的赛博城市。本来以为它只会画一个静态霓虹背景结果生成出来的页面里包含了一整套动态系统——城市会随机停电但又能自动启动备用电源、按优先级恢复亮灯甚至带雨滴、车流、广告牌闪烁效果。整座城市只用一个 index.html 就撑了起来双击就能跑。这篇文章不打算吹“AI 取代前端”而是把这次实验完整复盘从提示词设计、需求拆解、代码结构到运行效果、报错排查、工程化建议全部摊开讲。无论你是想用 AI 辅助做前端原型还是单纯想学单页面 Canvas 动画、状态机、定时任务调度这篇文章都值得收藏。1. 为什么“单文件 HTML Claude”值得研究1.1 单文件 HTML 的边界与优势单文件 HTML指的是把结构、样式、脚本全部写进同一个 index.html 文件里不依赖构建工具不需要 npm install也不需要本地服务器。用浏览器直接打开文件页面就能运行。这种形式在真实企业中看起来不够“正规”但在原型验证、教学演示、比赛 Demo、临时工具等场景下非常实用。它的优点很突出零依赖不需要安装任何环境。易分享一个文件复制给同事双击就能看。易排查所有代码在一个文件里打开搜索就能定位逻辑。适合 AI 生成上下文完整Claude 不需要跨文件理解一次性输出即可运行。当然缺点也很明显当项目超过一定规模一个文件里堆上千行代码可维护性会下降。所以单文件 HTML 最适合的场景是“把创意快速变成可交互的可视化原型”而不是承载复杂业务系统。1.2 会停电也会自救的赛博城市是什么在描述这个项目时“会停电也会自救”听起来像是一个带剧情的交互装置。拆到技术层面它大致包含这些子系统功能技术实现城市视觉Canvas 绘制动态元素如建筑、窗户、雨滴、车流霓虹灯效果CSS 动画、文字阴影、扫描线、模糊发光停电事件定时任务或随机事件切换城市状态自救机制备用电源启动、按优先级恢复供电、倒计时恢复交互入口按钮手动触发停电或恢复观察状态变化在这个项目里“停电”不只是一个黑屏效果而是一种状态“自救”也并非无脑恢复而是在状态机控制下先启动紧急照明再恢复基础设施最后点亮商业区。整个流程围绕一个核心变量展开当前城市处于什么状态。1.3 Claude 在前端生成中的定位Claude 生成前端代码的常见方式是接收自然语言描述后输出结构完整的 HTML/CSS/JS。它特别擅长“一次性生成小范围原型”因为单文件 HTML 的上下文相对集中AI 不容易在多个文件之间陷入混乱。不过需要明确Claude 是高效的“原型生成器”不是生产级代码自动生成器。它生成的内容可能出现 API 拼写错误、逻辑不闭合、性能考虑不足等问题。正确的用法是把它当作一个“需求转代码”的起点由你来做审查、修改、验证。2. 环境准备与提示词设计2.1 运行环境这个实验对环境要求很低操作系统Windows / macOS / Linux 均可。浏览器Chrome、Edge、Firefox 最新版。编辑器VS Code、Notepad甚至系统自带记事本都可以。不需要安装 Node.js、Python 或其他运行环境。如果你使用的是 Claude Web 端直接在对话框要求生成代码即可如果你的网络环境需要配合命令行或其他工具使用请根据实际情况调整本文重点演示代码思路不依赖特定工具的安装步骤。2.2 核心提示词模板我给 Claude 的提示词大概长这样你可以直接复制使用请帮我生成一个完整的单文件 HTML 页面主题是“赛博朋克城市模拟器”。 要求 1. 所有 CSS 和 JavaScript 都写在同一个 index.html 文件中。 2. 使用 Canvas 绘制城市夜景包括高楼、发光窗户、雨滴、车流。 3. 页面风格为赛博朋克背景使用深蓝色和紫色渐变色。 4. 页面顶部显示城市状态文本例如正常运行、停电中、备用电源启动、恢复中。 5. 页面有一个按钮可以手动触发停电。 6. 停电后城市会随机经过 3 到 8 秒恢复供电。 7. 恢复过程需要分阶段先恢复医院和应急灯再恢复住宅灯光最后恢复广告牌和霓虹灯。 8. 城市状态需要在 Canvas 和页面文本上同步显示。 9. 添加一个简单的 fsm 状态机状态包括 normal、blackout、restoring。 10. 请给出完整的可运行代码并添加必要的中文注释。这段提示词的关键点在于需求具体、状态明确、逻辑顺序清晰。如果你只是说“帮我做一个赛博城市”AI 可能给你一个完全静态的页面但当你把“停电”“自救”“分阶段恢复”写清楚生成结果会立刻变得有实验性。2.3 提示词设计技巧想让 Claude 生成高质量前端代码提示词里最好包含以下信息输出形态是单个文件还是多个文件。技术栈约束是否允许使用 Canvas是否允许第三方库。交互逻辑用户能做什么系统自动发生什么。状态变化有哪些状态如何切换。视觉风格色系、氛围、参考意象。可维护性要求是否要求注释、模块化、清晰命名。另外复杂需求不要一口气全部丢给 AI。更稳妥的做法是先让它生成“城市夜景”再迭代加“停电事件”最后加“恢复机制”。每轮只提出一个核心变化代码质量会更高。3. 把它拆开看单 HTML 里的城市系统3.1 状态机停电与自救的骨架一座会自救的城市核心不是一堆 if else而是一个简单的状态机。这个项目的状态主要有三个normal正常运行城市灯火通明。blackout停电中建筑变暗霓虹灯熄灭。restoring正在恢复分阶段点亮不同区域。用 JavaScript 表示// 城市状态机 const cityState { status: normal, // normal | blackout | restoring restoreStep: 0, blackoutTimer: null, restoreTimer: null }; // 切换状态 function changeState(nextStatus) { cityState.status nextStatus; console.log(城市状态 -, nextStatus); updateStatusText(); }当状态发生切换时所有依赖状态渲染的逻辑都会感知到。Canvas 每帧读取 cityState.status决定绘制哪些灯光页面文本同步显示状态。这样整个城市的行为就被“状态”统一驱动了。如果没有状态机停电逻辑和恢复逻辑散落在各处很容易出现“停电后广告牌还在闪”“恢复时全部灯一起亮”这类不协调问题。3.2 Canvas 渲染城市如何动起来Canvas 是单文件 HTML 实现动态城市的最佳工具。它通过 JavaScript 在画布上逐帧绘制图形可以画出高楼轮廓、窗口亮灯、雨滴、车流等。核心渲染循环使用 requestAnimationFrameconst canvas document.getElementById(cityCanvas); const ctx canvas.getContext(2d); // 让 Canvas 铺满窗口 canvas.width window.innerWidth; canvas.height window.innerHeight; // 城市建筑数组 const buildings []; for (let i 0; i 40; i) { buildings.push({ x: i * 80, y: canvas.height - 200 - Math.random() * 300, width: 60, height: 200 Math.random() * 300, windows: [] }); } // 每帧执行的绘制逻辑 function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawBackground(); drawBuildings(); drawRain(); requestAnimationFrame(draw); } draw();requestAnimationFrame 的好处是它会跟随浏览器刷新率进行绘制通常每秒 60 帧并且当页面切到后台时会自动暂停节省 CPU。相比之下setInterval 固定间隔容易造成掉帧或性能浪费。3.3 停电与自救逻辑事件怎么驱动画面停电与自救是一套异步调度逻辑。它的关键在于用定时器或随机事件触发状态变化然后改变 Canvas 绘制参数。一个简单实现思路// 随机停电调度 function startRandomBlackout() { const delay 4000 Math.random() * 6000; setTimeout(() { changeState(blackout); // 进入停电状态后安排恢复计划 startRestoreProcess(); // 继续循环 startRandomBlackout(); }, delay); } // 分阶段恢复 function startRestoreProcess() { setTimeout(() { changeState(restoring); }, 2000); setTimeout(() { cityState.restoreStep 1; // 医院应急灯恢复 }, 3000); setTimeout(() { cityState.restoreStep 2; // 住宅灯光恢复 }, 5000); setTimeout(() { cityState.restoreStep 3; // 霓虹灯恢复 changeState(normal); }, 7000); }这里的核心是状态切换并不直接绘制任何东西而是修改状态变量。真正的绘制决策最终由渲染循环中的条件判断决定。把调度逻辑和渲染逻辑分开代码会更容易理解。3.4 CSS 动画与视觉效果Canvas 负责主场景CSS 则可以增强页面的赛博朋克氛围。比如标题文字、按钮、状态栏都可以用 CSS 实现霓虹灯效果。一个典型的霓虹文字样式.city-status { font-size: 24px; color: #00ffff; text-shadow: 0 0 4px #00ffff, 0 0 12px #0077ff, 0 0 24px #0055ff; letter-spacing: 4px; }再用 CSS 动画实现闪烁keyframes flicker { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } .neon-light { animation: flicker 1.2s infinite; }需要注意的是CSS 动画适合做装饰性闪烁而城市中不同建筑窗户的“亮/灭”状态更适合在 Canvas 中按帧控制因为每个窗户都要参与状态逻辑。4. 完整实战从零到一造出会自救的赛博城市4.1 搭建页面骨架HTML当你让 Claude 生成这座城市时得到的结果应该是类似下面这种结构。我先给出整理后的 HTML 骨架便于阅读!-- 文件路径index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title赛博城市模拟器/title style /* 样式放这里 */ /style /head body header classcity-header h1 classcity-titleCYBER CITY/h1 div classcity-status idcityStatus正常运行/div button classaction-btn idtriggerBlackout触发停电/button /header canvas idcityCanvas/canvas script // JavaScript 放这里 /script /body /html这个骨架非常干净。header 是页面控制区canvas 是城市渲染主画布。Claude 在生成代码时通常会自动给出类似的布局你只需要根据需求调整。4.2 添加赛博风格样式CSS下面的样式覆盖了页面整体氛围/* 页面基础样式 */ * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #0a0015; font-family: Courier New, monospace; overflow: hidden; color: #00ffff; } .city-header { position: absolute; top: 0; left: 0; right: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; padding: 16px 24px; background: rgba(10, 0, 21, 0.7); backdrop-filter: blur(4px); border-bottom: 1px solid rgba(0, 255, 255, 0.3); } .city-title { font-size: 26px; letter-spacing: 6px; color: #fff; text-shadow: 0 0 6px #00ffff, 0 0 20px #00aaff; } .city-status { font-size: 18px; padding: 6px 14px; border: 1px solid rgba(0, 255, 255, 0.5); border-radius: 4px; background: rgba(0, 0, 0, 0.4); } .action-btn { padding: 8px 20px; font-size: 16px; font-family: inherit; color: #00ffff; background: transparent; border: 1px solid #00ffff; cursor: pointer; transition: all 0.2s; } .action-btn:hover { background: rgba(0, 255, 255, 0.2); box-shadow: 0 0 12px rgba(0, 255, 255, 0.5); }这里的 backdrop-filter 会形成毛玻璃效果增加现代感。text-shadow 则用于模拟霓虹灯发光。4.3 编写城市模拟核心JavaScript这是整个实验最核心的部分。由于单文件里代码较长我拆成三个逻辑片段并给出详细注释。第一部分状态管理。// 城市全局状态 const state { status: normal, // normal | blackout | restoring restoreStep: 0, // 恢复阶段0 未恢复1 应急2 住宅3 商业 alpha: 1 // 灯光亮度系数 }; // 更新页面状态文本 function updateStatusText() { const el document.getElementById(cityStatus); if (state.status normal) el.textContent 正常运行; else if (state.status blackout) el.textContent 停电中; else if (state.status restoring) el.textContent 恢复中; } // 修改状态并输出日志方便调试 function setStatus(nextStatus) { state.status nextStatus; console.log(状态切换 -, nextStatus); updateStatusText(); }第二部分绘制城市。const canvas document.getElementById(cityCanvas); const ctx canvas.getContext(2d); canvas.width window.innerWidth; canvas.height window.innerHeight; // 生成城市建筑 const buildings []; const BUILDING_COUNT 35; for (let i 0; i BUILDING_COUNT; i) { const height 180 Math.random() * 320; const width 50 Math.random() * 30; buildings.push({ x: i * (canvas.width / BUILDING_COUNT), y: canvas.height - height, width, height, windows: [] }); // 每栋建筑随机生成窗户 const cols Math.floor(width / 16); const rows Math.floor(height / 22); for (let c 0; c cols; c) { for (let r 0; r rows; r) { if (Math.random() 0.35) { buildings[i].windows.push({ cx: buildings[i].x 8 c * 16, cy: buildings[i].y 10 r * 22, size: 6, memory: Math.random() 0.5 }); } } } } // 绘制背景 function drawBackground() { const gradient ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, #150033); gradient.addColorStop(0.6, #1a0145); gradient.addColorStop(1, #050010); ctx.fillStyle gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); } // 绘制建筑和窗户 function drawBuildings() { buildings.forEach(building { ctx.fillStyle #12002a; ctx.fillRect(building.x, building.y, building.width, building.height); ctx.strokeStyle rgba(0, 255, 255, 0.15); ctx.strokeRect(building.x, building.y, building.width, building.height); building.windows.forEach(win { const lampOn state.status normal || (state.status restoring state.restoreStep 2); if (lampOn win.memory) { ctx.fillStyle #ffe889; ctx.shadowColor #ffd700; ctx.shadowBlur 4; } else if (state.status restoring state.restoreStep 1 win.memory) { ctx.fillStyle #66ff99; ctx.shadowColor #2ecc71; ctx.shadowBlur 4; } else { ctx.fillStyle rgba(30, 30, 30, 0.6); ctx.shadowBlur 0; } ctx.fillRect(win.cx, win.cy, win.size, win.size); }); ctx.shadowBlur 0; }); } // 绘制雨滴 function drawRain() { ctx.strokeStyle rgba(0, 200, 255, 0.25); ctx.beginPath(); for (let i 0; i 80; i) { const x (Math.random() * canvas.width i * 40) % canvas.width; const y (Math.random() * canvas.height i * 50) % canvas.height; ctx.moveTo(x, y); ctx.lineTo(x - 3, y 12); } ctx.stroke(); } // 主渲染循环 function draw() { drawBackground(); drawBuildings(); drawRain(); requestAnimationFrame(draw); } draw();第三部分停电与自救调度。// 手动触发停电 function triggerBlackout() { if (state.status ! normal) return; setStatus(blackout); state.alpha 0.2; state.restoreStep 0; startSelfRecovery(); } // 自动恢复过程 function startSelfRecovery() { setTimeout(() { setStatus(restoring); state.restoreStep 1; // 应急灯先亮 console.log(应急照明系统启动); }, 1500); setTimeout(() { state.restoreStep 2; // 住宅区恢复 console.log(住宅区供电恢复); }, 3500); setTimeout(() { state.restoreStep 3; // 商业区霓虹灯恢复 state.alpha 1; setStatus(normal); console.log(全城供电恢复); }, 5500); } // 循环随机停电 function startRandomBlackoutCycle() { const timer setTimeout(() { if (Math.random() 0.5) { triggerBlackout(); } startRandomBlackoutCycle(); }, 8000 Math.random() * 8000); } // 事件绑定 document.getElementById(triggerBlackout).addEventListener(click, triggerBlackout); window.addEventListener(resize, () { canvas.width window.innerWidth; canvas.height window.innerHeight; }); // 启动 startRandomBlackoutCycle();以上三段逻辑合成在一起就是一个完整的“会停电也会自救”的城市模拟器。你也可以让 Claude 把这三段合并为一个