关于 html-demo
使用 React 和 DOM/CSS 构建动画 HTML 窗口的技能,并提供 `scripts/step-window.js` 原生版本。适合需要可控网页应用演示而非视频录制的产品与 Web 团队;画面由时间决定,可跳转步骤、拖动进度、截图并呈现减少动态效果的结束状态。
主要功能
- 01按时间生成确定性画面
- 02步骤导航与进度拖动
- 03固定舞台并按比例缩放
- 04光标与动画辅助函数
- 05减少动态效果的结束画面
适用场景
- 01在网页中演示应用工作流
- 02在指定步骤截取一致的画面
- 03展示可拖动进度的交互演示
- 04为减少动态效果提供静态结束画面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mengto/ skills --skill html-demo安装后在 Claude Code 中输入/html-demo确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamldemo/index.htmlPROMPT.mdreferences/brief.mdcontract.mdfilm.mdscripts/example-steps.jsxpreview-plugin.mjsshoot.mjsstep-window.cssstep-window.jsStepWindow.jsx详细介绍
HTML 演示
React · DOM/CSS(纯原生版本见 scripts/step-window.js)。不使用视频、画布、WebGL 或屏幕录制。
它看起来像录制内容,但其实是 HTML。每一帧都是计算得出的,因此在任何尺寸和像素密度下都清晰锐利;其中的文字是真实文本。它还能循环播放、跳转到某个步骤、拖动进度、冻结以便截图,也能在减少动态效果模式下静止显示——这些都由同一份代码实现。
适用范围。 如果你需要将真实浏览器的真实录制内容制作成 MP4,请使用 browser-video-recording。如果要将页面制作成固定时长的影片,请使用 website-to-motion-design。如果要交付的是网页上的实时窗口,用来展示应用的运行过程,请使用此技能。
来源。 摘自 fil.design/download(2026 年 10 月)。Fil for Mac 的主视觉窗口完整展示了四个工作流。八个章节窗口逐步展示一项功能:导入 URL → Design → Agent;提示词 → 局部重绘 → 扩展画布 → 图层 → 角度;录制 → 动态设计 → 编辑 → 转录;网站;浏览器。一位负责人构建了引擎和主视觉,另有一位辅助 Agent 根据共享的简要说明构建了每个章节窗口。
实现原理
应用的每一帧都是由一个数字决定的纯函数。 步骤的 View({ t }) 仅根据 t(即进入该步骤后的秒数),在固定的 1200 × 750 设计尺寸内绘制窗口中的所有内容。然后通过一个 CSS 缩放值(--k = width / 1200),将该舞台适配到窗口。
如果移除这一机制,演示就会出问题。如果状态依赖 setTimeout 链、动画回调或副作用,就无法跳转到某个步骤。页面在隐藏标签页后会出现偏差,不同次运行的截图会不一致,而减少动态效果模式下的画面会停在半途。在窗口内进行响应式设计则会以另一种方式出问题:文字会随宽度变化而重新排版,指针会点不中目标,手机上显示的也会是另一个应用。
import StepWindow, { Cursor, ease, typed, glide } from './scripts/StepWindow.jsx';
const ASK = 'The Ashen One, a knight in blackened armour resting on his sword';
function Prompt({ t }) { // t: 0 → dur, nothing else
const text = typed(ASK, t, 0.9, 2.6); // typed between 0.9 s and 2.6 s
const made = ease(t, 3.4, 5.2); // the picture draws in
const c = glide(t, [[0, 0, 900, 600], [0.2, 0.8, 520, 330], [2.8, 3.3, 760, 470]], [0.85, 3.35]);
return (<>
<App prompt={text} reveal={made} /> {/* the app's real markup at 1200 × 750 */}
<Cursor {...c} /> {/* last, over everything */}
</>);
}
export const STEPS = [
{ id: 'prompt', name: 'Prompt', dur: 6, View: Prompt },
// 3–5 steps, 4–8 s each
];
<StepWindow steps={STEPS} tabs={['The Ashen One']} label="Make a picture" />
scripts/StepWindow.jsx 提供窗口、时钟(useStepClock)、缩放、步骤栏、Cursor,以及 ease、typed、cue、held 和 glide 这些辅助函数。完整 API 见 references/contract.md;主视觉变体(包含多种流程、摄像机和节拍)见 references/film.md。
工作流
- 选定一个示例,并在所有地方使用它。 选择一个可信且归你所有的主题,例如产品、角色或网站(Fil 使用了 The Ashen One 和 Ashgrove),并让每个窗口都围绕它展开。绝不要展示客户、合作伙伴或真实人物的画布。满是无法展示的真实姓名的窗口毫无用处,而每个窗口都使用不同主题则会显得零碎。
- 动笔之前,先收集真实 UI 的参考资料。 使用截图、文档中的录屏(用
ffmpeg -ss 2 -i in.mp4 -frames:v 1 out.png提取帧)以及应用的源代码。为每个步骤列出参考资料。用 grep 从源代码中复制每个标签、菜单项、toast 和费用,绝不要凭记忆。如果熟悉该应用的观众发现一个虚构的词(“Processing…”),他们就不会再相信其他内容。 - 先在纸上编排步骤。 每个窗口包含 3–5 个步骤,每步 4–8 s,总计 18–30 s。每个步骤都要留出时间让观众看到起始状态(0.3–1 s)、操作、按应用实际方式出现的结果(先是进行中的状态,再显示结果),并至少停留 1 s 展示结果。步骤名称应与栏中显示的一致,使用 1–3 个词(“粘贴链接”“局部重绘”)。
- 先用命名时间编写每个步骤,再根据
t推导状态。 使用TYPE_FROM = 1.9, SEND_AT = 4.3, DONE_AT = 6.9这类常量,然后进行比较(t >= SEND_AT);所有会动的内容都用ease(t, a, b),输入文字用typed(),变更列表用cue()。把时间常量放在顶部,这样编排过程读起来像脚本,也能在一处调整时间。 - 根据这些状态,以 1200 × 750 绘制应用。 尽可能复用真实应用的标记和 CSS。否则,从它的样式表中复制尺寸:侧栏宽度、顶栏高度、圆角、字号。为每个类和
@keyframes添加窗口专属前缀(ximp-、xpic-),并用 grep 确认没有其他地方使用这些前缀。 - 像真人一样编排指针动作。 点击前 0.3–0.6 s 移到目标位置,按下 0.15–0.2 s,在目标之间滑动 0.5–0.9 s。操作间隙让指针停在不碍事的位置;观众应该看结果时,让指针淡出。指针必须对准真实元素(从 DOM 测量
data-aim,或通过截图核对坐标)。 - 拍摄、对比、修正:至少三轮。 在每个步骤中每隔 0.5–1 s 抽取一帧(
scripts/shoot.mjs),并与参考资料并排对照。问问自己:使用这个应用的人会相信这是一段录屏吗?先修正结构(布局、尺寸、文案),再打磨细节(动效)。 - 检查边缘情况。 查看减少动态效果模式下的结束帧、手机屏幕宽度(390 px 和 340 px)、隐藏的标签页重新显示时的情况,以及控制台。
规则,以及每条规则要避免的问题
- 仅从
t绘制;让 CSS 过渡只负责平滑状态变化。 过渡取决于前一帧。因此,任何在拖动时间轴、冻结或截图时必须精确的内容(进度、输入的文本、揭示效果、计数器、摄像机目标)都应由t决定。悬停效果、面板滑动和指针滑动可以使用过渡。代价是,拖动时间轴后,这些过渡会延迟结束,最长可达其自身的持续时间。指针也必须精确时,请使用glide()。 - 固定舞台,统一缩放。 按 1200 × 750(16:10,即窗口的
aspect-ratio)设计,并通过ResizeObserver使用transform: scale(var(--k))缩放整个舞台,同时设置transform-origin: 0 0。窗口内部的响应式布局会移动指针目标,导致应用在不同设备上的显示各不相同。 - 以应用实际呈现结果的方式展示结果。 先展示进行中的状态(微调器、“正在制作……”、微光效果、流动的线条),再展示结果,最后呈现定格效果。Fil 的图片从顶部逐渐显现:在一条 2 px 的暖色扫描线上方,将
clip-path: inset(0 0 100% 0)过渡为inset(0);底下铺一层暗化的图片副本(blur(10px) brightness(0.5) saturate(0.4),缩放至 1.08)。随后用 0.9 s 从brightness(1.35) saturate(1.1)过渡至稳定状态,并加上闪光效果。结果如果只是突然出现,看起来就像幻灯片。 - 使用真实结果和真实文字。 展示应用实际生成并保存在页面旁边的输出。不要使用替代内容或占位文本。如果某个步骤需要你没有的结果,就在真实应用中生成,并先向负责人确认是否会产生费用。人们最先注意到的就是编造的结果。
- 诚实地压缩时间。 持续数分钟的运行过程可以在几秒内播放,但数字必须真实。Fil 的主视觉中,演示每经过 1 秒就显示 18 s(“已用 2:14 制作 5 个内容”),并在每个步骤结束时扣减额度余额。
- 只有在画面可见时才播放。 使用
IntersectionObserver(窗口的阈值设为 0.3,大型主视觉设为 0.15),在document.hidden时跳过计时,将dt限制在 0.25 s,并每隔 40–50 ms 计时一次。只有帧的签名发生变化时才重新渲染;主视觉使用JSON.stringify(frame)进行比较。内部的视频随可见性播放和暂停。否则,页面上有 8 个窗口时,即便它们不在屏幕上也会消耗 CPU;切回标签页时,还会直接快进完一个步骤。 - 减少动态效果时,应显示经过设计的结束帧。 在
prefers-reduced-motion: reduce下,将每个步骤显示为t = dur,关闭舞台上的所有动画和过渡,并确保步骤栏仍可正常使用(点击后显示该步骤的结束画面)。因此,每个结束帧都应独立构成完整、出色的画面。显示半成品画面或空白窗口就是失败。 - 每个窗口都使用独立前缀,包括类名和关键帧。 两个窗口都定义
.card或@keyframes in时会发生冲突,后加载的样式表会更改另一个窗口的样式。只有在同时加载两个窗口的页面上,你才会看到这个问题。 - 在布局效果中从 DOM 测量目标。 不要硬编码目标。布局完成后,找到指针的目标(
[data-aim="headline"])和摄像机的取景框(聚焦节点的offsetLeft/Top),再除以--k。硬编码的坐标会在字体首次加载或标签内容更改时偏移。 - 将摄像机绑定到测量时所在的场景。 在包含多个画布的影片中,将摄像机变换与场景索引一起保存,并且只将其应用于对应场景。否则,下一个场景会沿用上一个场景的变换,在舞台上横冲直撞。
- 通过滞后的视图交叉淡化场景。 场景切换时,继续绘制旧场景的最后一帧,直到其 300 ms 淡出结束,然后再显示新场景。按步骤 ID 为舞台设定 key,让每个步骤都以 0.45 s 淡入。没有滞后时,新场景会在旧场景淡出期间闪现。
- 在演示内部加载真实字体。 在窗口内绘制的页面(如落地页或网站)应自行加载网页字体;如果缺少
<link id="…">,就添加一个。字体回退的字形度量会改变换行位置,导致指针目标偏移。 - 在当前场景播放时预加载下一场景的图片(
new Image().src = …)。否则,缓存未命中时,下一个场景会先出现空白框。 - 将其标记为供辅助技术识别的图片。 为舞台设置
role="img"和列出步骤的aria-label,并在舞台上保留pointer-events: none和user-select: none。步骤栏使用带有aria-current="step"和可见焦点环的真实按钮。屏幕阅读器应朗读“导入页面:粘贴链接、在 Design 中打开、使用 Agent 编辑”,而不是 400 个虚假的 UI 节点。 <figure>需要设置margin: 0。 浏览器默认的 40 px 左右外边距会让 390 px 宽的手机少掉 80 px 可用空间。
已交付的数值
| 项目 | 数值 |
|---|---|
| 舞台 | 1200 × 750,--k = clientWidth / 1200 |
| 步骤 | 每个窗口 3–5 步,每步 4–8 s,每个窗口 18–30 s |
| 结果停留时间 | ≥ 1 s |
| 输入文字 | ~50 个字符/s(1.8 s 输入 90 个字符);有人会读,不会有人等它输入完 |
| 指针滑动 | 0.5–0.9 s,cubic-bezier(0.55, 0, 0.25, 1);提前 0.3–0.6 s 到达 |
| 点击 | 按下 0.15–0.2 s;箭头缩放 1.15 → 0.98 |
| 步骤淡入 | 0.45 s,cubic-bezier(0.2, 0.7, 0.1, 1) |
| 场景交叉淡化 | 下一场景绘制前延迟 300 ms |
| 时钟 | 每 40 ms 跳动一次(窗口)或每 50 ms 一次(影片);dt ≤ 0.25 s;屏幕显示阈值为 0.3(主视觉为 0.15) |
| 线条 | 1.6 px,透明度 0.8,颜色取自源节点;活动状态:虚线 6 7,以 0.7 s 的周期流动;静止状态(短暂、熟练):0.14 |
| 摄像机 | 对正在执行的步骤及其输入内容最多放大至 1.2×;整个画布缩放为 0.86–1.08×;滑动时长 1.3 s,cubic-bezier(0.45, 0, 0.2, 1) |
| 显示的时间 | 每秒演示时间显示 18 s 的应用时间 |
同时处理多个窗口
先自己构建引擎和一个窗口。这个窗口就是风格指南。然后给每位助手分配一个窗口,并提供共享简报(references/brief.md)。每位助手只能创建自己的两个文件,使用各自的前缀;通过一个共享开发服务器(scripts/preview-plugin.mjs,位于 /preview?entry=)预览;在各自的临时目录中工作,不运行 git。每位助手至少进行三轮录制、评审和修复,然后报告步骤、截图路径以及“仍有哪些不足”。
阅读这些报告:他们列出的不足就是下一轮要解决的问题。助手达到使用限制后会停止。请发送消息让他们继续,而不要启动新的助手,因为他们此前的工作会保留。
验证
- 冻结帧(
useStepClock(steps, { start: { i, t } }))在相同的t下,每次运行看起来都一样。 - 每隔 0.5–1 s 都录制每个步骤,并与真实应用的参考图并排对比。
- UI 中的每个标签、菜单项和数字都能在应用源码或参考资料中找到。
- 减少动态效果时会显示完整的结束帧,步骤栏仍然可用。
- 页面滚出视野或标签页被隐藏时会暂停,恢复后不会快进。
- 在 390 px 和 340 px 的宽度下都不会横向滚动,且窗口保持 16:10 的比例。
- 类名和关键帧只使用窗口自身的前缀(通过 grep 检查)。
- 控制台没有错误。
文件
scripts/StepWindow.jsx、scripts/step-window.css:引擎(React):窗口、时钟、缩放、步骤栏、指针、辅助工具。scripts/step-window.js:同一引擎的无 React 版本(HTMLDemo.mount),使用相同的 CSS。scripts/example-steps.jsx:基于约定编写的三个步骤(提示词 → 生成 → 完整视图)。scripts/preview-plugin.mjs:提供/preview?entry=<file>的 Vite 插件,可单独预览每个窗口。scripts/shoot.mjs:使用 Playwright 在指定秒数或冻结帧截图,倍率为 2×。references/contract.md:步骤约定、时钟 API 和辅助工具。references/film.md:主视觉变体(根据图表安排流程,按提示设置节拍,并使用经过测量的摄像机)。references/brief.md:供助手使用的共享简报模板。demo/index.html:独立的演示页面(原生实现),展示 Fil for Mac 制作图片,并带有拖动条。