别再用 Markdown 了!用一个 HTML 文件搞定架构图、流程图、交互原型
当 AI 输出遇上"纯文本天花板",一个 Skill 让你的产出从黑白文档跃升为可交互的视觉页面。
引子:我们为什么需要 HTML Artifact?
你有没有遇到过这样的场景:让 AI 帮你画一张系统架构图,它给你吐了一屏 Mermaid 代码或者一段文字描述,你看完之后......一脸茫然。或者你想做个方案对比,结果收到一个巨长的 Markdown 表格,滑了三屏都没滑完。
问题的根源在于:很多信息天然就不适合用纯文本表达。架构图需要空间布局,流程图需要箭头连线,方案对比需要并排陈列,状态报告需要进度条和颜色编码。Markdown 在这些场景下就像用锤子拧螺丝------能用,但别扭。
html-artifact 这个 Skill 的思路非常直接:既然浏览器是最强大的渲染引擎,那就生成一个单文件 HTML,所有 CSS 写在 <style> 里,所有 JS 写在 <script> 里,所有 SVG 内联。没有构建步骤、没有 npm、没有框架 CDN。双击就能打开,发邮件就能分享,提交到仓库就能存档。
这篇文章会完整介绍这个 Skill 的设计思路、设计系统、11 种制品类型以及动画架构图的实现细节,内容较长,建议收藏慢读。
Skill 概览
html-artifact 的核心定位是:生成自包含、零依赖的 HTML 文件,用视觉化的交互页面替代纯文本输出。
它覆盖的场景包括但不限于:
- 空间信息:架构图、流程图、模块依赖图
- 对比分析:方案并排、设计选项、前后对照
- 结构化报告:状态更新、事故时间线、PR 评审
- 交互原型:动画演示、参数调优、点击跳转
- 自定义编辑 UI:优先级看板、配置编辑器、Feature Flag 管理
- 演示文稿:会议用的幻灯片
- 概念解释:带折叠展开和术语表的文档
触发方式也很自然------当你对 AI 说"画个架构图""做个方案对比""生成报告""做个 demo""可视化一下"的时候,它就会启动。甚至你没明确要求 HTML,但输出明显更适合视觉呈现时,它也会自动介入。
核心原则
单文件、零依赖
这是整个 Skill 最重要的约束。每个产出物都是一个 .html 文件,所有样式和脚本内联,不引用任何外部资源。唯一的例外是:当布局极其复杂时,允许引入 Tailwind CDN(<script src="https://cdn.tailwindcss.com">),但大多数场景下推荐手写 CSS。
内容优先,不是装饰优先
颜色、图标、动画都是为了传达信息,不是为了好看。一个有色边框意味着某种语义,一个图标信号代表某个类别,动画展示的是数据流方向。不用占位符,不用 Lorem ipsum,填充的是用户的真实数据。
设计系统
html-artifact 内置了一套完整的设计规范,保证所有产出物风格统一、专业美观。
调色板
基调是暖色、纸质感、专业范:
css
:root {
/* 背景 */
--ivory: #FAF9F5; /* 页面背景 --- 暖白 */
--paper: #FFFFFF; /* 卡片/面板背景 */
/* 主色 */
--slate: #141413; /* 主文本、深色 UI 元素 */
--clay: #D97757; /* 强调色、CTA、高亮 */
--clay-d:#B85C3E; /* clay 的悬停深色变体 */
/* 辅助色 */
--oat: #E3DACC; /* 柔和强调、次级边框 */
--olive: #788C5D; /* 成功、正面指标 */
--rust: #B04A3F; /* 错误、失败、负面指标 */
/* 灰阶 */
--g100: #F0EEE6; /* 微弱背景、斑马纹 */
--g200: #E6E3DA; /* 内部边框、分割线 */
--g300: #D1CFC5; /* 卡片边框、输入框边框 */
--g500: #87867F; /* 次要文本、标签、弱化内容 */
--g700: #3D3D3A; /* 正文(比 slate 稍柔和) */
}
语义用色总结:页面背景用 --ivory(暖白),卡片用 --paper(纯白),标题用 --slate(近黑),正文用 --g700,标签和说明文字用 --g500,主要强调色是 --clay(陶土色),成功/正面用 --olive(橄榄绿),错误/负面用 --rust(铁锈红)。
在架构图场景中,每个功能角色还有专属配色:
bash
Cyan #0891B2 --- 入口、客户端、路由
Purple #7C3AED --- LLM、AI、推理
Pink #BE185D --- 调度、编排
Green #15803D --- 服务端执行、工具
Orange #D97706 --- 沙箱、隔离执行
Teal #0E7490 --- 用户/客户端节点
节点背景保持白色,角色颜色体现在图标边框、图标描边色、激活态描边和粒子动画的填充色上。
字体栈
全部使用系统字体,不加载任何外部字体:
css
:root {
--serif: ui-serif, Georgia, "Times New Roman", Times, serif;
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--mono: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
}
排版规则如下:
- 页面标题 (h1) :
--serif,34-38px,font-weight: 500,letter-spacing: -0.018em - 章节标题 (h2) :
--serif,27px,font-weight: 500 - 卡片标题 :
--serif,19px - 正文 :
--sans,14.5px,line-height: 1.55 - 描述文字 :
--sans,13.5px,color: var(--g700) - 眉标 / 类别标签 :
--mono,11-12px,letter-spacing: 0.08-0.12em,全大写,color: var(--g500) - 代码 / 数据 / 文件路径 :
--mono,11-13px - Badge / Tag :
--mono,10-11px
其中"眉标"(eyebrow)是一个高频复用的模式------标题上方的小号大写标签:
css
.eyebrow {
font-family: var(--mono);
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--g500);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 12px;
}
.eyebrow::before {
content: "";
width: 24px;
height: 1.5px;
background: var(--clay);
}
布局与间距
页面整体布局:
css
body {
margin: 0;
background: var(--ivory);
color: var(--slate);
font-family: var(--sans);
line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
.wrap {
max-width: 1120px;
margin: 0 auto;
padding: 0 32px 140px;
}
间距遵循一套统一的数值体系:4px(紧凑间距)→ 8px(小间距)→ 12-14px(组件内边距)→ 20px(卡片内边距/网格间距)→ 32px(页面水平内边距)→ 48px(小节间距)→ 72px(大节上边距)→ 80px(头部上边距)。
组件模式
卡片------最核心的容器组件:
css
.card {
background: var(--paper);
border: 1.5px solid var(--g300);
border-radius: 14px;
overflow: hidden;
transition: transform 150ms ease, box-shadow 150ms ease, border-color 150ms ease;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(20, 20, 19, 0.10);
border-color: var(--slate);
}
Badge / 标签:
css
.badge {
font-family: var(--mono);
font-size: 10.5px;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--g300);
background: var(--paper);
color: var(--g500);
}
.badge.accent {
border-color: var(--oat);
background: #FFF7ED;
color: var(--clay-d);
}
Tab 标签页 、进度条 、侧边面板等组件也都有标准化的实现,此处不再逐一展开。
响应式规则
内容最大宽度 1120px 居中。920px 以下双栏布局折叠为单栏;768px 以下对比网格折叠为单栏;640px 以下移除缩进、缩减间距。SVG 图表使用 viewBox + preserveAspectRatio="xMidYMid meet" + 百分比宽度容器,不使用固定像素宽度。
11 种制品类型
html-artifact 定义了 11 种标准制品类型,覆盖了日常开发中最常见的可视化需求。下面逐一介绍。
1. Side-by-Side Explorer(并排比较器)
适用场景:对比 2-4 种方案、设计或实现。
结构上用 CSS Grid 等宽列排列,每个选项一张卡片,包含选项编号 Badge、方案名称、描述文字、代码示例(如有)、优缺点列表。推荐方案用 border-color: var(--clay) 高亮边框标识,优点用 --olive,缺点用 --rust。底部附一张汇总表,沿关键维度横向比较所有方案。
2. Annotated Diff(带标注的代码差异)
适用场景:代码变更展示、PR Review。
采用主面板 + 侧边栏布局。主面板按文件分段展示差异行(添加行绿色、删除行红色、上下文行灰色),行内可插入标注块。标注有四级严重度:Critical 用 --rust 底色加粗边框,Warning 用 --clay 底色,Suggestion 用 --olive 底色,Info 用 --g100 底色。侧边栏展示变更统计和文件快速跳转导航。
3. Interactive Flowchart(交互流程图)
适用场景:部署流水线、决策树、业务流程。
基于 SVG,支持点击节点弹出详情侧面板。节点类型包括:方形(流程节点,圆角矩形)、菱形(决策节点,四点 path)、药丸形(终止节点,rx=半高)、成功态(绿色填充 + 绿色描边)、失败态(红色填充 + 红色描边)。连线类型:默认实线灰色带箭头,通过分支绿色实线,失败分支红色虚线。底部始终包含图例。
4. Animated Architecture Diagram(动画架构图)
这是最复杂也是最有价值的制品类型,后面会单独详细讲解。
5. Arrow-Key Slide Deck(键盘翻页幻灯片)
适用场景:快速会议演示。
每个 <section> 是一张幻灯片,默认隐藏,.active 类控制显示。左右/上下方向键翻页,右下角显示页码。通常 5-10 页,每页只讲一个要点,使用大号衬线标题,尽量少的文字,需要图解时内联 SVG。整个翻页逻辑只有约 20 行 JS。
6. Structured Report(结构化报告)
适用场景:周报、项目进度、仪表盘。
顶部是眉标+日期,接着 3-4 张 KPI 卡片(数值 + 标签 + 变化指标),然后按"已完成""进行中""风险/阻塞"分段展示,进行中的用进度条,风险项用红色标记。可选内嵌 SVG 小型图表。
7. Incident Timeline(事故时间线)
适用场景:事故复盘、Post-mortem。
顶部是严重度横幅(P1/P2/P3 配色),接着 TL;DR 摘要框,主体是纵向时间线------左侧时间戳、中间状态圆点、右侧事件详情和日志摘录(等宽字体块)。底部是影响指标和行动项清单。
8. Collapsible Explainer(可折叠解释器)
适用场景:技术概念解释、功能文档。
顶部 TL;DR 摘要,主体用 <details>/<summary> 做可折叠章节,代码示例用 Tab 切换不同语言/配置,侧边栏或底部放术语表,需要图解时内联 SVG。
9. Drag-and-Drop Board(拖拽看板)
适用场景:任务排优先级、Bug 分拣、看板式管理。
多列布局,卡片可在列间拖拽,用原生 HTML5 Drag and Drop API 实现。关键设计:始终包含一个"导出"按钮,点击后将当前排列输出为 Markdown 或 JSON,方便粘贴回对话或提交。
10. Config Editor(配置编辑器)
适用场景:Feature Flag 管理、参数调优。
开关按类别分组排列,有依赖警告(如 Flag A 关闭时 Flag B 不可用),"复制差异"按钮只导出修改过的值,大量配置项时支持搜索过滤。
11. Module Map(模块地图)
适用场景:代码库导览、模块关系展示。
SVG 方框加箭头展示模块间依赖关系(箭头方向是 import 方向),热路径用强调色高亮,入口模块用特殊 Badge 标记。点击模块弹出侧面板展示其导出项、关键函数和文件列表。
动画架构图:最硬核的部分
动画架构图是整个 Skill 中技术含量最高的制品类型。它能把一个系统的请求流转过程用动画粒子可视化出来,让人一眼就能理解数据是怎么在各个服务之间流动的。
SVG 分层架构
SVG 的绘制顺序就是层叠顺序,后绘制的在上面。html-artifact 要求严格按以下 6 层从底到顶排列:
xml
Layer 0: <defs> --- 图案、箭头标记、滤镜定义
Layer 1: <rect> --- 纯色背景填充
Layer 2: <rect> --- 网格图案叠加
Layer 3: <g> --- 区域边界矩形(虚线轮廓,逻辑分组)
Layer 4: <g> --- 连接路径(节点间的线条)
Layer 5: <g> --- 节点组(rect + foreignObject)
Layer 6: <g id="particles"> --- 动画粒子(JS 管理)
核心原则:连线在节点下方(这样节点视觉上"坐在"连线上面),粒子在最上层(移动的小圆点始终可见)。搞反这个顺序是最常见的视觉 bug。
foreignObject:最关键的技术
这是整个架构图质量的决定性技术。绝不使用 SVG <text> 渲染节点内的多行内容 ,而是用 foreignObject 在 SVG 坐标系中嵌入真正的 HTML:
svg
<g>
<!-- 可点击/可设样式的矩形 -->
<rect data-node-id="my-node"
x="220" y="320" width="170" height="88" rx="12"
fill="#FFFFFF" stroke="#E5E0D8" stroke-width="1"/>
<!-- HTML 内容叠加层,位置和尺寸完全一致 -->
<foreignObject x="220" y="320" width="170" height="88">
<div xmlns="http://www.w3.org/1999/xhtml"
style="width:100%; height:100%; display:flex; align-items:center;
gap:10px; padding:8px 12px; font-family:system-ui,sans-serif;">
<!-- 图标容器 -->
<div style="flex-shrink:0; width:36px; height:36px; border-radius:8px;
background:#FFF7ED; border:1px solid #FED7AA;
display:flex; align-items:center; justify-content:center;">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18"
viewBox="0 0 24 24" fill="none" stroke="#0E7490"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- 图标路径 -->
</svg>
</div>
<!-- 文字内容 -->
<div style="min-width:0; flex:1;">
<div style="font-size:13px; font-weight:600; color:#1F1B16;
white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
Node Title
</div>
<div style="font-size:11px; color:#6B6258; margin-top:2px;
display:-webkit-box; -webkit-line-clamp:2;
-webkit-box-orient:vertical; overflow:hidden;">
描述文字 · 更多细节
</div>
</div>
</div>
</foreignObject>
</g>
为什么这很重要?SVG <text> 做不了:自动换行、文字截断、Flexbox 布局、富文本格式。foreignObject 让你在 SVG 坐标系内享受完整的 HTML/CSS 能力。rect 提供边框、填充和点击目标,foreignObject 提供内容渲染。两者共享完全相同的 x/y/width/height。
多实例堆叠
当一个节点代表多个实例(比如一组服务器集群)时,在主卡片后面堆叠 2-3 个偏移矩形:
svg
<g>
<!-- 影子卡片 2(最后面) -->
<rect x="230" y="310" width="170" height="88" rx="12"
fill="#FFFFFF" opacity="0.85" stroke="#E5E0D8" stroke-width="1"/>
<!-- 影子卡片 1 -->
<rect x="225" y="315" width="170" height="88" rx="12"
fill="#FFFFFF" opacity="0.67" stroke="#E5E0D8" stroke-width="1"/>
<!-- 主卡片(最前面) -->
<rect data-node-id="cluster" x="220" y="320" width="170" height="88" rx="12"
fill="#FFFFFF" stroke="#E5E0D8" stroke-width="1"/>
<foreignObject x="220" y="320" width="170" height="88">
<!-- HTML 内容 -->
</foreignObject>
</g>
每张影子卡片相对前一张偏移 (+5, -5) 像素,透明度递减:0.85、0.67。如果节点处于"休眠/未创建"状态,使用弱化配色:fill="#F4EFE7" stroke="#D6D0C4",文字颜色改为 #A8A29E。
连接路径
连线端点对齐到节点边缘而非中心,这是常见的计算错误来源:
arduino
右边缘:(node.x + node.width, node.y + node.height/2)
左边缘:(node.x, node.y + node.height/2)
上边缘:(node.x + node.width/2, node.y)
下边缘:(node.x + node.width/2, node.y + node.height)
线型语义:实线 = 同步/直接调用,虚线 (stroke-dasharray: 6 5) = 异步/事件驱动,红色虚线 = 错误/失败路径。
虚线流动动画
让虚线看起来在"流动"的技巧,纯 SVG 实现,零 JS:
svg
<path d="M 830 350 C 880 330, 893 316, 923 256"
fill="none" stroke="#A8A29E" stroke-opacity="0.85"
stroke-width="1.4" stroke-dasharray="6 5" stroke-linecap="round">
<animate attributeName="stroke-dashoffset"
values="22;0" dur="1.2s" repeatCount="indefinite"/>
</path>
values="22;0" 中的 22 是 dash+gap(6+5=11)的两倍,保证循环平滑。dur="1.2s" 是合适的默认速度。
数据粒子动画
这是架构图最酷炫的部分:用 JS 驱动小圆点沿路径移动,模拟请求/数据在系统中的流转。
每个粒子由 3-4 个 SVG 元素组成:光晕圆(大半径低透明度)、实心点(描边白色)、拖尾点(小半径更低透明度)、可选的标签(用 rect+text 组合)。
移动逻辑借助 SVGPathElement.getTotalLength() 和 getPointAtLength() 实现:
js
function animateParticle(pathId, color, label, duration) {
const path = document.getElementById(pathId);
const totalLen = path.getTotalLength();
const particleGroup = document.getElementById('particles');
const halo = createCircle(0, 0, 10, color, 0.06);
const dot = createCircle(0, 0, 5.5, color, 1);
dot.setAttribute('stroke', '#FFF');
dot.setAttribute('stroke-width', '1');
particleGroup.append(halo, dot);
let start = null;
function step(timestamp) {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
// 缓入缓出
const t = progress < 0.5
? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
const point = path.getPointAtLength(t * totalLen);
halo.setAttribute('cx', point.x);
halo.setAttribute('cy', point.y);
dot.setAttribute('cx', point.x);
dot.setAttribute('cy', point.y);
// 起始淡入、结束淡出
const opacity = progress < 0.1 ? progress / 0.1
: progress > 0.9 ? (1 - progress) / 0.1 : 1;
dot.setAttribute('opacity', opacity);
halo.setAttribute('opacity', opacity * 0.06);
if (progress < 1) {
requestAnimationFrame(step);
} else {
halo.remove();
dot.remove();
}
}
requestAnimationFrame(step);
}
粒子颜色按数据类型区分:请求/入口用 Cyan #0891B2,消息/上下文用 Purple #7C3AED,工具调用用 Pink #BE185D,服务端结果用 Green #15803D,沙箱结果用 Orange #D97706。
播放控制
对于多阶段流程的动画图,会提供"重置""下一阶段""播放"按钮,把整个数据流转过程拆成若干步骤,逐步演示。
常见错误清单
使用这个 Skill 或者自己按此思路制作 HTML Artifact 时,以下是最容易踩的坑:
- 用 SVG
<text>写多行内容 → 应该用foreignObject+ HTML div - 连线端点对准节点中心 → 应该对准节点边缘
- 层级顺序搞反 → 路径在节点下方,粒子在节点上方
- 引入外部依赖 → 除了 Tailwind 特殊情况外不引 CDN
- 使用占位内容 → 填充用户的真实数据
- 全图只有一种颜色 → 每种角色/类型分配不同强调色
- 没有图例 → 有颜色/线型语义的图必须附图例
- 没有交互 → 至少加点击高亮或悬停效果
完整示例:最小动画架构图
下面是一个可以直接运行的完整 HTML 文件,展示两个节点之间的数据流动:
html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Architecture Demo</title>
<style>
:root {
--ivory:#FAF9F5; --paper:#FFFFFF; --slate:#141413;
--clay:#D97757; --g300:#D1CFC5; --g500:#87867F; --g700:#3D3D3A;
--serif: ui-serif, Georgia, serif;
--sans: system-ui, sans-serif;
--mono: ui-monospace, "SF Mono", Menlo, monospace;
}
* { box-sizing: border-box; margin: 0; }
body { background: var(--ivory); color: var(--slate);
font-family: var(--sans); padding: 40px 24px; }
.wrap { max-width: 1000px; margin: 0 auto; }
h1 { font-family: var(--serif); font-size: 28px; font-weight: 500;
margin-bottom: 20px; }
.canvas { border: 1.5px solid var(--g300); border-radius: 14px;
background: var(--paper); overflow: hidden; }
button { font-family: var(--mono); font-size: 12px; padding: 8px 16px;
background: var(--paper); border: 1.5px solid var(--g300);
border-radius: 8px; cursor: pointer; }
button:hover { border-color: var(--slate); }
button.primary { background: var(--clay); color: white;
border-color: var(--clay); }
</style>
</head>
<body>
<div class="wrap">
<h1>System Architecture</h1>
<div style="display:flex; gap:8px; margin-bottom:16px;">
<button onclick="reset()">重置</button>
<button class="primary" onclick="runDemo()">▶ 播放</button>
</div>
<div class="canvas">
<div style="aspect-ratio:800/400; width:100%;">
<svg viewBox="0 0 800 400" style="width:100%;height:100%;"
preserveAspectRatio="xMidYMid meet">
<defs>
<pattern id="grid" width="40" height="40"
patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none"
stroke="#E5E0D8" stroke-opacity="0.5" stroke-width="1"/>
</pattern>
</defs>
<rect width="800" height="400" fill="#FAF7F2"/>
<rect width="800" height="400" fill="url(#grid)"/>
<!-- 连接路径 -->
<path id="p1" d="M 270 200 C 350 200, 450 200, 530 200"
fill="none" stroke="#A8A29E" stroke-width="1.4"
stroke-dasharray="6 5" stroke-linecap="round">
<animate attributeName="stroke-dashoffset"
values="22;0" dur="1.2s" repeatCount="indefinite"/>
</path>
<!-- 节点 A -->
<g>
<rect data-node-id="a" x="100" y="156" width="170" height="88"
rx="12" fill="#FFF" stroke="#E5E0D8" stroke-width="1"/>
<foreignObject x="100" y="156" width="170" height="88">
<div xmlns="http://www.w3.org/1999/xhtml"
style="width:100%;height:100%;display:flex;align-items:center;
gap:10px;padding:8px 12px;font-family:system-ui;">
<div style="font-size:13px;font-weight:600;color:#1F1B16;">
Service A</div>
</div>
</foreignObject>
</g>
<!-- 节点 B -->
<g>
<rect data-node-id="b" x="530" y="156" width="170" height="88"
rx="12" fill="#FFF" stroke="#E5E0D8" stroke-width="1"/>
<foreignObject x="530" y="156" width="170" height="88">
<div xmlns="http://www.w3.org/1999/xhtml"
style="width:100%;height:100%;display:flex;align-items:center;
gap:10px;padding:8px 12px;font-family:system-ui;">
<div style="font-size:13px;font-weight:600;color:#1F1B16;">
Service B</div>
</div>
</foreignObject>
</g>
<!-- 粒子层 -->
<g id="particles"></g>
</svg>
</div>
</div>
</div>
<script>
function createCircle(cx,cy,r,fill,opacity){
const c=document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx',cx);c.setAttribute('cy',cy);
c.setAttribute('r',r);c.setAttribute('fill',fill);
c.setAttribute('opacity',opacity);return c;
}
function animateParticle(pathId,color,dur){
const path=document.getElementById(pathId);
const len=path.getTotalLength();
const g=document.getElementById('particles');
const halo=createCircle(0,0,10,color,0.06);
const dot=createCircle(0,0,5.5,color,1);
dot.setAttribute('stroke','#FFF');dot.setAttribute('stroke-width','1');
g.append(halo,dot);
let start=null;
function step(ts){
if(!start)start=ts;
const p=Math.min((ts-start)/dur,1);
const t=p<.5?2*p*p:1-Math.pow(-2*p+2,2)/2;
const pt=path.getPointAtLength(t*len);
halo.setAttribute('cx',pt.x);halo.setAttribute('cy',pt.y);
dot.setAttribute('cx',pt.x);dot.setAttribute('cy',pt.y);
const op=p<.1?p/.1:p>.9?(1-p)/.1:1;
dot.setAttribute('opacity',op);halo.setAttribute('opacity',op*.06);
if(p<1)requestAnimationFrame(step);
else{halo.remove();dot.remove();}
}
requestAnimationFrame(step);
}
function activateNode(id,color){
const n=document.querySelector(`[data-node-id="${id}"]`);
if(n){n.setAttribute('stroke',color);n.setAttribute('stroke-width','2');}
}
function reset(){
document.querySelectorAll('[data-node-id]').forEach(n=>{
n.setAttribute('stroke','#E5E0D8');n.setAttribute('stroke-width','1');
});
document.getElementById('particles').innerHTML='';
}
function runDemo(){
reset();
activateNode('a','#0891B2');
setTimeout(()=>animateParticle('p1','#0891B2',1500),300);
setTimeout(()=>activateNode('b','#15803D'),1800);
}
</script>
</body>
</html>
把这段代码保存为 .html 文件,双击打开,点"播放"按钮,你就能看到一个蓝色小圆点从 Service A 沿着虚线流向 Service B,同时两个节点依次高亮。这就是"unreasonable effectiveness of HTML"的缩影。
总结
html-artifact 这个 Skill 的价值在于它系统性地解决了"AI 输出的可视化天花板"问题。它不是简单地"生成一个网页",而是沉淀了一整套经过验证的设计系统和工程实践:统一的色彩语言、严格的字体层级、标准化的组件模式、可复现的 SVG 动画技术。
如果你在用 AI 辅助日常工作,下次需要画架构图、做方案对比、写状态报告、甚至做会议 PPT 的时候,不妨试试让 AI 生成一个 HTML Artifact。双击打开的那一刻,你会理解为什么纯文本不够用。