Communitygithub.com

构建HTML原型

当用户请求交互式或可点击的HTML原型、模拟图或产品UI流程、屏幕状态或功能的演练(供设计人员、产品经理、开发人员等评审者使用)时使用——如“原型”、“可点击模拟图”、“展示屏幕状态”。

构建HTML原型 是什么?

此技能用于快速构建可交互的HTML原型,专注于验证单一设计决策,而非展示所有屏幕。它引导用户遵循一套严谨的设计流程:从理解用户上下文、定义核心问题、编写场景脚本(包括分支和边界情况),到与需求提出者达成一致,最后使用Koobiq组件库实现高保真原型。该技能与Claude Code等AI代理深度集成,通过读取Markdown文件(SKILL.md)了解工作流,并通过脚本命令自动拆解/组装单个HTML文件。原型支持多状态切换、动态注释、评审者反馈和应用变体(Variant),方便设计评审。核心优势在于强制以决策为中心的设计、通过结构化问题清单提前暴露假设、以及自动化的屏幕映射和验证检查,显著提升原型设计的效率和质量。

兼容平台✓Claude Code✓Codex CLI✓Cursor✓Windsurf
npx skills add https://github.com/mikeozornin/skills/tree/HEAD/skills/mo-build-html-prototype

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

构建 HTML 原型

所有原型都构建在此技能的 assets/template.html 文件中:一个独立的 HTML 文件。该模板已包含完整外壳:页眉、浏览器框架、状态切换器、备注面板、屏幕地图、评审者评论、带有 k-* 组件的 Koobiq 令牌以及运行时。外壳 UI 根据操作系统/浏览器语言选择英语或俄语;原型本身的文案由你编写。你只需编写 PROTOTYPE 块 (JS) 和第 5 节“PROTOTYPE STYLES”(CSS)。不要破坏模板:切勿重建、重设样式或描述外壳。

设计先于编码

原型是用于单一决策的工具,而非屏幕画廊。在编写 prototype.js 之前,请完成以下步骤。

  1. 收集上下文。 用户是谁(角色、专业领域),他们正在执行什么任务,触发条件是什么,以及对他们来说“完成”是什么样子。如果产品已存在(代码库、截图、Figma、文档、或早期原型),则从中提取其导航、术语、实体和数据,并仅设计变更部分。不要围绕功能凭空发明产品。

  2. 命名问题。 用一句话(大型原型可用几句话)说明:评审者必须决定什么,或者 UI 中最可疑的地方是什么(用户能否找到 X、理解 Y、Z 在真实数据下是否成立)。对于每个问题,记录评审者需要看到什么才能回答它,以及哪些状态或数据可能破坏设计。如果请求中未命名问题,则将风险最高的决策作为问题,并在步骤 4 中向作者提出。

  3. 编写场景,使用用户的语言,从触发条件到结果:“分析师看到峰值 → 打开规则 → 降低阈值 → 查看将会产生多少事件”。然后仅添加问题所需的分支:取消、验证错误、空状态或首次使用,以及那些无人预见到但可能出现的硬情况:无权限、与其他对象的冲突、限制、大量数据、对象当前状态禁止的操作。步骤变为 STEPS,分支变为状态,横切条件(角色、功能开关、数据量)变为 SWITCHES。

  4. 在构建前与作者达成一致。通过一条简短消息发送:

    • 问题;
    • 你计划展示的场景及其中包含的分支和硬情况;
    • 所有你猜测而非从请求或产品中获取的信息,作为编号列表,每项一条陈述,以便作者按编号回答:“2——不,需要经理批准”。

    等待回答。对于对现有原型的小修改,可跳过此步骤。如果作者要求不要提问,则基于你的猜测构建,并在回复中列出它们。

  5. 设置深度 为每个步骤 × 状态单元格:

    • 完整 = 在问题的路径上:可点击,每个状态,压力数据;
    • 合理 = 上下文:静态但真实;
    • 排除 = 在回复中声明未覆盖。

    仅使用问题所需的状态:理想状态、空状态或首次使用、部分状态、错误、无权限,每个步骤最多约 6 个状态。仅在展示特定内容(长操作、进度、部分结果)时添加加载状态。加载失败很少值得作为一个状态,但“此对象状态下不允许操作”通常值得。切勿伪造未设计的结果。

  6. 先以文字形式设计每个屏幕: 其目的(一句话)、一个主要操作,以及用户在此处需要看到才能做出决策的内容(按重要性排序)。然后使用标准 Koobiq 模式(表格、表单、侧面板、模态框、空状态)进行构建。仅在问题所在处引入新颖性;其他所有内容都应像产品通常的屏幕。

    • 每个操作都有一个可见的结果:发生了什么以及对象现在在哪里。
    • 破坏性操作应指明对象和后果。按钮通常应显示动词。
    • 错误信息应说明如何修复并保留用户输入。
    • 在屏幕、面包屑、提示和 URL 中,同一事物使用相同的名称。UI 文案应使用用户的语言,如果安装了 ux-write 则遵循之。
  7. 在视图之前编写数据。 使用能够对布局施加压力的真实领域数据:一个长名称、0/1/多个的数量、一个罕见状态。使用固定日期和值(不要使用 Date.now() / Math.random()),并且不要使用 Lorem Ipsum。

变体,仅当问题是“哪个解决方案”时使用。在 VARIANTS 中构建 2–3 个(最多 5 个)变体,而不是替评审者选择。

  • 它们在结构(布局、层次结构、主要操作)上不同,而非颜色或文案。如果两个草图看起来相似,则重新制作一个,并明确禁止某种模式(“不得使用卡片网格”)。
  • 变体可以共享数据,但绝不能共享布局。
  • 为每个变体提供一个 title 和一个单行描述 text。
  • 最佳反馈是“表头来自 B,列表来自 C”。

完成标准

在回复之前,对照以下各项检查原型:

  • 每个显示的路径从头至尾运行。 它从用户遇到触发条件的地方开始,而不是从登录页面或仪表盘开始。每个显示的分支都以返回路径或到达结果结束。移交给其他产品或系统应是一个存根,带有可见结果(提示,或类似“在 Jira 中创建任务”的状态),切勿复制其他 UI。次要控件可以不做任何事情(菜单项可以仅是 close),只要没有场景从它开始。
  • 有争议的部分已标记。 每个更改或存在争议的元素都带有注释。当问题是采用哪个解决方案时,应有 2–3 个在结构上不同的变体。
  • 无多余内容。 每个状态、开关和注释要么有助于回答问题,要么测试关于需求的猜测。删除其余内容。

构建

  1. 获取可编辑部分。 SKILL_DIR 是此技能的目录,A=$SKILL_DIR/scripts/assemble.mjs(使用 node 运行,无依赖),工作目录例如 /tmp/proto-<name>/:

    • 新原型:node $A --split $SKILL_DIR/assets/template.html /tmp/proto-<name>;
    • 修改现有原型:node $A --split <file>.html /tmp/proto-<name>。重新组装也会升级其外壳。

    这会生成 prototype.js(演示 PROTOTYPE 块)和 prototype.css(第 5 节)。

  2. 重写 prototype.js,替换所有演示内容:PROTO、STEPS、SWITCHES、S、NOTES、LINKS(通常为 [])、数据、VIEWS、ACTIONS。自定义 CSS 放入 prototype.css。分轮次进行:

    1. 主路径,从头到尾可点击,处于理想状态;
    2. 其他状态和分支;
    3. 注释;
    4. 验证并对照完成标准检查。

    在路径可以正常工作之前,不要打磨单个屏幕。

  3. 组装: node $A <target>.html /tmp/proto-<name>/prototype.js /tmp/proto-<name>/prototype.css。它会检查语法和必需的声明。仅交付 HTML。

  4. 视图: 每个步骤一个 VIEWS[step](S),返回一个 HTML 字符串,并根据 S.state / S.sw 进行分支。使用 k-* 类构建;参见 references/koobiq-cheatsheet.md。

    • 每个状态在直接打开时都应完全渲染。 直接打开(弹出、哈希链接、地图缩略图、备注、←/→、protoCheck)从初始的 S 加上该状态的 seed 开始,例如 { id: 'confirm', title: '确认', seed: { name: '暴力破解:RDP' } }。流程内的 goto() 会保留用户输入。VIEWS 仅读取 S 和常量数据。
    • 覆盖层。 模态框或侧面板绘制在它所覆盖的屏幕之上,位于同一视图内。一次性覆盖层(如确认框)是该步骤的一个状态(form/confirm)。具有自己子流程(表单 → 错误 → 确认)的覆盖层可以是自己的步骤,并在其下绘制被覆盖的屏幕。对于面板上的模态框,先渲染面板,再渲染模态框:最后一个 .k-overlay 是最顶层的,只有其标记显示。
    • 滚动。 比浏览器框架更高的屏幕在框架内部滚动,如同页面。新步骤从顶部开始;同一步骤上的新状态或变体保持滚动位置,以便评审者在同一位置进行比较。应用外壳(.app-body + .app-content)在 .app-content 内部滚动,和之前一样。
    • 使用 data-act → ACTIONS 绑定点击。仅通过 goto(step, state) / data-goto 切换屏幕,以使状态按钮、URL 栏和哈希保持同步。
  5. 注释(3–7 条) 仅放在问题所在的元素上:即被更改或存在争议的元素。

    • 在元素上放置 class="changed" data-anchor="note-N",并在其内部放入 ${marker(N)}。将空白或禁用元素包裹在 <span class="k-anchor changed" data-anchor="note-N"> 中。
    • 注释 text 用 1–2 句话说明更改了什么以及为什么。如果是假设,以“检查:…”结尾。
    • try 表示点击什么可以看到它。
  6. 屏幕地图 根据状态和转换自动构建。转换来源于每个状态的标记:data-goto,以及在 ACTIONS 中其启用的控件、菜单项和顶层覆盖层调用的字面 goto() / openState() 调用(禁用控件不产生箭头)。在同一操作中计算的状态,如果其可能的 ID 以字符串形式出现在那里,则有效:goto('list', S.q ? 'empty' : 'data')。通过辅助函数、定时器或用户未点击的事件触发的转换,需放入 LINKS:{ from: 'report/building', to: 'report/ready', label: '报告就绪' }。

  7. 验证(参见验证部分)。修复并重新组装,直到 protoCheck() 干净为止,然后对照完成标准检查原型。

  8. 回复 时包含文件路径、步骤 × 状态表、故意未覆盖的内容,以及在达成一致后做出的任何猜测。不要描述外壳。

评审者评论

用户可能会粘贴“复制全部”中的 Markdown。它包含 File: 行上的文件名。每个 ## 是一个屏幕及其哈希(#step/state?switches);变体以 Variant B · Cards → 开头,哈希包含 variant=<id>。外壳使用评审者的 UI 语言编写,因此可能是俄语:Файл:、Вариант B。每个 ### 是一个元素,包含其在 #view 内的选择器、其 data-* 属性以及关联的注释编号,后跟引用格式的评论。

应用评论时,在 VIEWS[step] / ACTIONS 中找到该文件(该变体)中的元素,然后通过 --split 编辑并重新组装到同一文件中:评论存在于浏览器 localStorage 中,以文件路径为键。

外壳规则

  • 页眉 = 仅短标题。 框架上方任何位置不得出现副标题、标题、图例或“流程原型…”等文字。
  • 单一状态控件: “步骤·状态”弹出。无上一步/下一步步进器、每屏分段控件、框架上方选项卡或自定义快捷键(内置了 ←/→、Esc 和变体选择器 1–9)。
  • 注释仅存在于 aside#notes 中,.browser 之外。 在 .app 内部只有编号标记。切勿将注释文本放入 ::after 工具提示、覆盖层或应用内的“评审模式”列中。
  • 不要编辑 RUNTIME,也不要移动或重设舞台、浏览器、注释、评论或屏幕地图的样式。行为放入 ACTIONS。
  • 默认使用 Koobiq: k-* 类和 --kbq-* 令牌。不要使用十六进制/rgb 颜色、自创调色板(--bg、紫色强调色)以及仅亮色模式样式,因为主题切换必须正常工作。如果使用其他设计系统,则保留外壳,仅重设应用的样式。
  • 一个文件,无外部 JS。

PROTOTYPE API

内容方法
标题 / URL 栏PROTO.title、PROTO.url(S)
弹出网格STEPS[{id,title,states[{id,title,seed?}]}]、SWITCHES[{id,title,options}]
状态S.step、S.state、S.sw[id]、S.menu(打开的菜单 id),以及你自己的字段
导航goto(step, state, patch?) 在流程内使用(保留 S);标记 data-act="goto" data-goto="form/errors"。openState(step, state) = 直接打开(初始 S + seed)
动作data-act="name" → ACTIONS.name(el, e);重新渲染,除非返回 false
内置动作goto;close(S.menu = null);menu + data-menu="id"(切换);set + data-field + data-value(写入 S,关闭菜单)。菜单也可通过 Esc、外部点击和其他任何动作关闭
输入data-model="field" 写入 S.field;data-live 在输入时重新渲染(焦点和光标位置保留);data-live="act" 首先调用 ACTIONS.act;data-scroll="key" 保持滚动位置
焦点goto('form', 'errors', { focus: 'name' }) 一次性聚焦 [data-model="name"]
辅助函数icon('search')、marker(n, '--x:100%;--y:50%')、tip(html, text, 'k-float--right')、matches(S.q, ...fields)、toast(title, {kind, text})、esc(str)
注释NOTES[{id, step, state, title, text(html), try}]:step/state 指明锚点所在位置
屏幕地图自动构建;LINKS[{from: 'step/state', to: 'step/state', label}] 添加扫描无法看到的转换
变体VARIANTS[{id, title, text, STEPS?, NOTES?, VIEWS?, ACTIONS?, LINKS?, S?, url?}]。全局声明为变体 A;变体仅覆盖有差异的部分(S 与全局 S 合并)。每个变体有自己的屏幕地图、注释和评论;SWITCHES 以及前进/后退是共享的。选择器在页眉中,键 1–9

验证

在后台启动服务器(通常 file:// 被阻止),在浏览器工具中打开打印的 URL 加上 <file>.html。每次重新组装后重新加载,因为仅哈希更改不会重新加载文件。

cd <dir> && node -e "const fs=require('fs'),T={'.html':'text/html; charset=utf-8','.js':'text/javascript','.css':'text/css','.svg':'image/svg+xml'};require('http').createServer((q,r)=>{const p='.'+decodeURIComponent(new URL(q.url,'http://x').pathname);fs.readFile(p,(e,d)=>{r.writeHead(e?404:200,{'Content-Type':T[require('path').extname(p)]||'application/octet-stream'});r.end(e?'Not found':d)})}).listen(0,'127.0.0.1',function(){console.log('http://127.0.0.1:'+this.address().port+'/')})"
  1. 在页面中运行 protoCheck()(控制台或浏览器工具评估),窗口宽度为 1440 或更大。它必须返回 ok: true。它会打开每种步骤 × 状态 × 开关组合(在两个主题下),并报告渲染错误、空屏幕、溢出、损坏的标记或注释、未知的 data-act / data-goto / goto() / LINKS 目标、硬编码颜色以及模板遗留物。修复每个警告(相同状态、无效开关、非确定性数据、3–7 条之外的注释、未连接到其他步骤的步骤)或在回复中解释它。
  2. 在意大利面条模式下检查它(在浏览器工具中检查每个非预期的布局问题)。然后再次对照完成标准检查。

相关技能