AI 写的 HTML 原型全是空壳?聊聊真实 DOM 交互避坑

7 阅读9分钟

前两天让 Coding Agent 帮我写了一个客户筛选与批量分配的 HTML 原型页,页面外观、表格间距和按钮圆角看着都挺像样。结果打开浏览器准备自己先走一遍流程时,刚点开搜索框用中文输入法打字,按下回车选词的瞬间表单直接误提交了;紧接着点开“分配成员”弹窗,按 Esc 关不掉,点背景遮罩没反应,表格里的下拉操作菜单还被外层滚动容器裁切掉了一半。

更尴尬的是,当我在网页代码里翻原因时,发现刚才点完提交后弹出的那个“操作成功”提示,压根不是真的校验通过,而是 Agent 给按钮绑了一句固定的假 Toast——不管输入什么内容、选没选人,点一下就提示成功。

那晚我把这页原型彻底拆了一遍,才意识到 AI 生成 HTML 原型最容易踩的三个坑:

  • 用空点击和统一 Toast 冒充功能:为了看起来“能点”,模型随手给几个按钮挂上 alert 或固定成功提示,掩盖了真实表单校验、禁用态和条件分支的缺失;
  • 忽略真实浏览器的交互细节:中文输入法组合输入时按回车直接触发提交、弹窗打开后焦点乱跳、局部刷新把刚填好的输入框全部清空;
  • 用透明遮罩层盖住原生控件:在原型工作台里套一层绝对定位的透明热点框去做页面跳转,结果把原本 HTML 里的输入、悬浮、滚动和局部控件全部遮死了。

原型是拿来验证业务规则和操作手感的,不是做给截图看的静态画皮。让 AI 写 HTML 原型时,到底怎么把真实 DOM 交互稳稳留下来?


一、根因剖析:为什么 AI 写的 HTML 原型经常“一看就会、一点就假”?

复盘最近用多个 Coding Agent 迭代中后台原型的踩坑记录,假交互往往出在三个结构性问题上:

  1. 模型默认用“演示最短路径”敷衍复杂控件:
    只要你在提示词里没有明确禁止“空点击和统一 Toast”,Agent 很容易为了省事,用假弹窗、写死的数据数组甚至不可输入的 div 模拟表单。评审时稍微换个测试输入或测一下异常路径,整个页面当场穿帮。
  2. 工作台代理层与页面原生 DOM 抢控制权:
    很多原型预览器为了支持打点批注或画布连线,会在页面外层蒙一层透明热区或替身输入框。一旦热点覆盖了真实按钮,或者拖动排序直接销毁重建了 DOM 节点,页面里原本的下拉框、滚动条、输入法选字状态就会全部失效或丢失。
  3. 缺少分层的交互写法,简单状态也硬写脆弱脚本:
    其实很多中后台原型里的 Tabs 切换、折叠详情、基础弹窗显示隐藏,根本不需要几十行容易和跳转脚本打架的 DOM 操作代码;反过来,真正需要校验和键盘响应的表单,又缺少输入法组合判断和原生弹窗焦点处理。

二、新旧模式对比:透明替身假交互 vs 元形 v0.4.16 真实 DOM 保留方案

在开源原型工作台 元形 (yuanxing) v0.4.16 中,我把“HTML 真实交互保留”和“跨页流程控制”彻底拆开了边界:

对比维度传统 AI 生成假原型 / 透明热点覆盖元形 (yuanxing) v0.4.16 真实 DOM 方案
控件响应真实性透明替身热点遮挡页面,输入、悬浮、滚动常失效HTML 用真实 DOM 交互,截图才用热点,保留输入与滚动
输入法与键盘体验中文输入法按回车选词直接误提交,弹窗无焦点管理识别 event.isComposing 与 229,原生 <dialog> 规范收放
假反馈防呆边界随手挂统一成功 Toast 或空点击,掩盖规则缺口明确禁止透明替身、空点击与统一 Toast 冒充功能
简单页内交互成本每个 Tab、弹窗都手写一堆容易冲突的 JS 事件提供 data-yx-* 声明式局部交互,零脚本完成显隐与回显
跨页跳转与扩展隔离页内脚本与播放器跳转逻辑互相打架、状态丢失绑定跳转控件由播放器接管,扩展支持原生控件直连

三、实操落地:3 步让 AI 产出的 HTML 原型保持真实可测的 DOM 交互

在元形 v0.4.16 的项目目录中,不需要引入重型前端框架,按下面 3 步就能把原型从“静态假壳”拉回“真实可用”。

第一步:在表单与弹窗脚本中补齐真实浏览器边界(输入法 + 原生弹窗)

凡是涉及搜索框回车提交、弹窗确认和浮层菜单的页面,在生成 HTML 时直接约束下面两项基础细节——尤其是中文输入法组合输入和原生 <dialog> 焦点约束:

// 开源原型工作台: github.com/JasperLin2333/yuanxing | 在线体验: jasperlin2333.github.io/yuanxing
// 一键安装 Skill: npx skills add JasperLin2333/yuanxing --skill yuanxing

const keywordInput = document.getElementById('customer-search');
keywordInput.addEventListener('keydown', (event) => {
  // 防止中文输入法敲回车选字时误触发提交(兼容 Safari keyCode 229)
  if (event.isComposing || event.keyCode === 229) return;
  if (event.key === 'Enter') {
    event.preventDefault();
    runCustomerFilter(keywordInput.value.trim());
  }
});

// 阻断式确认弹窗优先使用原生 dialog.showModal(),自带 Escape 与焦点约束
const assignDialog = document.getElementById('assign-dialog');
document.getElementById('open-assign').addEventListener('click', () => {
  assignDialog.showModal();
});

这样做完之后,至少不会再出现“打字选词按回车直接提交表单”或者“弹窗打开后焦点还停在背后表格里”的尴尬体验。同时,下拉菜单如果放在局部滚动容器里,记得把浮层定位到视口可用边缘内,避免被 overflow: hidden 裁掉一半。

第二步:简单显隐、Tab 与跨页上下文回显,用声明式属性替代脆弱脚本

对于“点击打开弹层、切换历史记录 Tab、把输入的客户名称带到下一页展示”这类高频局部交互,在元形里甚至可以完全不写 JavaScript,直接用声明式属性表达:

<!-- artifact/pages/assign.html -->
<button data-yx-action="show" data-yx-target="#assign-panel">分配负责成员</button>

<section id="assign-panel" hidden>
  <input data-yx-bind="customerName" aria-label="客户名称" placeholder="请输入客户名称" />
  <button data-yx-action="hide" data-yx-target="#assign-panel">取消</button>
</section>

<!-- 同一页或跨页跳转后直接回显当前演示上下文 -->
<p>当前待分配客户:<span data-yx-text="customerName"></span></p>

<button data-yx-action="tab" data-yx-target="#tab-history" data-yx-group="[data-yx-panel]">变更历史</button>
<section id="tab-history" data-yx-panel hidden>最近一次分配记录...</section>

如果同一个 HTML 页面被画布上的两个节点复用(比如“默认列表”和“自动弹出分配面板”),只需在第二个节点上配置 initialContext: {"dialog": "open"},并在页面对应区域加上 data-yx-initial-visible="dialog" data-yx-value="open"。进入节点时按初始参数决定是否展开,之后完全交还给页面内部点击控制,不会在用户输入到一半时又强制弹回初始状态。

第三步:区分“页内控件”与“跨页跳转”,禁止透明替身并保留嵌入状态

把页面接入 artifact/workbench.json 后,只需守住两条同步边界:

  1. 跨页跳转控件不要双写逻辑:
    在 edges 里用稳定选择器(比如 selector: "[data-yx-id='confirm-assign']")绑定跨页跳转后,该按钮的跳转就由工作台播放器统一接管,不要在页面内 <script> 里再自己写一套 location.href 或历史跳转。
  2. 局部重绘和拖拽排序不销毁 iframe 输入状态:
    需要把文档里嵌入的原型重置回初始状态时,使用嵌入框底部自带的局部刷新按钮,它会单独重置当前嵌入项并保留文档滚动位置和其他画板的填写数据;工作台底层也在局部更新与排序时尽量保留未变节点和输入焦点,避免因为一次轻微调整就把整个页面重载清空。
# 开源仓库: github.com/JasperLin2333/yuanxing | 一键安装: npx skills add JasperLin2333/yuanxing --skill yuanxing
# 打开或复用本地可编辑工作台(仅首次启动开页,不重复弹标签)
node "<Skill目录>/scripts/yuanxing.mjs" open --root "projects/customer-assign" --reuse

# 确认改动后导出单文件离线原型,真实 DOM 交互完整保留
node "<Skill目录>/scripts/yuanxing.mjs" export --root "projects/customer-assign" --view prototype --replace

四、时间账与协作收益对比

把这套真实 DOM 约束放进日常的原型生成和评审流程后,我和开发对齐交互细节的返工成本降得很明显:

协同环节过去做法(假交互 + 透明热区)现在做法(元形 v0.4.16 真实 DOM 保留)效率变化
评审现场试操作中文打字误提交、下拉被裁切,讲一半被迫切代码解释原生输入法防护 + 真实 <dialog> + 完整下拉响应一次跑通真实路径
Tab/表单状态回显写一堆临时 JS 容易和跳转冲突,改字段要拆脚本data-yx-* 声明式属性 + 节点初值复用同一 HTML零冲突且少写 70% 胶水代码
异常流与边界暴露到处都是固定成功 Toast,提测才发现校验规则没定禁止空点击和假 Toast,设计阶段就把边界暴露出来减少联调返工
离线包交付验收离开本地服务很多模拟脚本失效单文件离线 HTML 完整保留输入、Tab 与上下文回显打开即测零依赖

总结

做可交互 HTML 原型,最怕的不是界面不够炫,而是表面看起来什么都能点,真上手填两个字就露馅。

用真实 DOM 控件代替透明替身,在表单里守住输入法组合和原生弹窗焦点,再用声明式属性承接常规显隐与跨页回显——把这些微小的浏览器现实考虑进去,AI 画出来的才算是一个能真正拿去评审、测试和交付的原型,而不是一张会动的空壳截图。

开源原型工作台 元形 (yuanxing) 已更新至 v0.4.16,如果你也在用 Coding Agent 做产品原型与交互式需求文档,可以直接装进工作区试试:

# 一键安装元形 Skill 到当前 AI 助手工作区
npx skills add JasperLin2333/yuanxing --skill yuanxing

大家平时让 AI 生成 HTML 原型时,还遇到过哪些“看着能点、一用就穿帮”的假交互坑?欢迎在评论区或 GitHub Issue 里聊聊。