SVG scenes

用 template svg: 把 Interact controls / Computed / Timer 接到声明式 SVG 场景——画弹簧、摆、行星、电路、任何 2D 物理 / 几何动画。 比 Vega-Lite chart 更自由(任意形状),但需要你写 SVG 知识。


When to use

  • ✅ 物理仿真(弹簧 / 摆 / 行星轨道 / 阻尼振动)
  • ✅ 几何演示(三角函数 / 圆周运动 / 反射折射 / 几何变换)
  • ✅ 电路 / 流体 / 光路示意图(动态参数)
  • ✅ 自定义图标 / 数据可视化(vega 表达不出来的形状)
  • ✅ 简单角色动画(机器人挥手、表情变化、玩具运动)
  • ❌ 数据图表(折线 / 柱状 / 散点)→ 用 Vega-Lite,简洁很多
  • ❌ 复杂 3D(SVG 是 2D;要 3D 用 WebGL / Three.js,Rho 不直接支持)
  • ❌ 像素画 / 位图(用 <img> 而不是 SVG)

Basic syntax

\```interact
slider angle 0 360 0 1
template svg:
<svg viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40" fill="none" stroke="#888" stroke-width="0.5"/>
  <line x1="50" y1="50"
        x2="{50 + 40*cos(angle*pi/180):.2f}"
        y2="{50 + 40*sin(angle*pi/180):.2f}"
        stroke="red" stroke-width="2"/>
</svg>
\```

template svg: 后是 SVG markup——{var} 占位符在任何属性里都可被替换。

渲染:圆 + 一条从圆心射出的红线,拖 angle 滑块红线绕圈转。


占位符在 SVG 属性里

{expression} 可以出现在 任何 SVG 属性值里,求值后插入:

<circle cx="{50 + 40*cos(angle*pi/180):.2f}"
        cy="{50 + 40*sin(angle*pi/180):.2f}"
        r="{radius}"
        fill="{isActive ? 'red' : 'blue'}"
        opacity="{alpha:.2f}"/>

mini DSL 支持的运算(详见 Computed)—— 三角函数 / 幂 / 三元式都可在 {} 里。


安全(SVG sanitizer)

Rho 内置 SVG sanitizer,自动剥离危险元素 / 属性:

被剥离 原因
<script> 任意 JS 执行
<foreignObject> 嵌入 HTML(可能 XSS)
on* 事件属性(onclick / onload / 等) 内联 JS
href 含 javascript: URL XSS
<image href="external://..."> 防外部资源跟踪

保留:

  • 标准 SVG 形状(rect, circle, line, path, polygon, ellipse, polyline)
  • 文本(text, tspan)
  • 容器(g, defs, symbol, use)
  • 渐变 / 模式(linearGradient, radialGradient, pattern)
  • 标准属性(cx, cy, x, y, width, height, fill, stroke, transform 等)
  • <title> / <desc>(accessibility 友好)
  • <animate> / <animateTransform>(标准 SVG 动画)

安全设计意味着 SVG 是 完全声明 + 不可执行 JS 的——这是 Rho 整体安全立场的延伸。


Examples

Example 1:圆周运动(pin + 滑块)

\```interact
slider angle 0 360 0 1
slider radius 10 50 40 1
template svg:
<svg viewBox="0 0 100 100" width="200" height="200">
  <circle cx="50" cy="50" r="{radius}" fill="none" stroke="#888" stroke-width="0.5" stroke-dasharray="2,2"/>
  <line x1="50" y1="50"
        x2="{50 + radius*cos(angle*pi/180):.2f}"
        y2="{50 + radius*sin(angle*pi/180):.2f}"
        stroke="red" stroke-width="1.5"/>
  <circle cx="{50 + radius*cos(angle*pi/180):.2f}"
          cy="{50 + radius*sin(angle*pi/180):.2f}"
          r="3" fill="red"/>
</svg>
\```

渲染:虚线圆 + 红线 + 红点。两个滑块同时控半径和角度。

Example 2:自动旋转(timer 配 SVG)

\```interact
timer angle 0 360 2 loop
template svg:
<svg viewBox="0 0 100 100" width="150" height="150">
  <g transform="translate(50, 50)">
    <circle r="40" fill="none" stroke="#888"/>
    <g transform="rotate({angle:.0f})">
      <line x1="0" y1="0" x2="40" y2="0" stroke="red" stroke-width="2"/>
      <circle cx="40" cy="0" r="3" fill="red"/>
    </g>
  </g>
</svg>
\```

渲染:自动旋转的红线 + 红点(每 50ms 转 2°,全圈约 9 秒)。用 SVG <g transform="rotate(...)"> 做旋转,比 cos/sin 直接算更直观。

Example 3:弹簧振子完整仿真

\```interact
timer t 0 10 0.05 loop
slider amplitude 5 40 25 1
slider period 0.5 4 2 0.1
template svg:
<svg viewBox="0 0 200 100" width="400" height="200">
  <line x1="20" y1="50" x2="180" y2="50" stroke="#ccc" stroke-dasharray="3,3"/>
  <line x1="100" y1="20" x2="100" y2="80" stroke="#aaa" stroke-width="0.5"/>
  <text x="100" y="15" text-anchor="middle" font-size="6" fill="#888">平衡位置</text>
  <circle cx="{100 + amplitude * sin(2*pi*t/period):.2f}" cy="50" r="6" fill="red"/>
</svg>
\```

渲染:水平线(地面)+ 平衡位置标记 + 红球做简谐运动。两个滑块控振幅和周期,timer 驱动时间。

Example 4:太阳系简化模型

\```interact
timer t 0 1000 1 loop
template svg:
<svg viewBox="0 0 200 200" width="400" height="400">
  <!-- 太阳 -->
  <circle cx="100" cy="100" r="8" fill="orange"/>

  <!-- 地球轨道 -->
  <circle cx="100" cy="100" r="40" fill="none" stroke="#ccc" stroke-width="0.5" stroke-dasharray="2"/>
  <circle cx="{100 + 40*cos(t*0.1):.2f}" cy="{100 + 40*sin(t*0.1):.2f}" r="4" fill="blue"/>

  <!-- 火星轨道 (周期更长) -->
  <circle cx="100" cy="100" r="60" fill="none" stroke="#ccc" stroke-width="0.5" stroke-dasharray="2"/>
  <circle cx="{100 + 60*cos(t*0.05):.2f}" cy="{100 + 60*sin(t*0.05):.2f}" r="3" fill="red"/>

  <!-- 木星轨道 (周期更长) -->
  <circle cx="100" cy="100" r="80" fill="none" stroke="#ccc" stroke-width="0.5" stroke-dasharray="2"/>
  <circle cx="{100 + 80*cos(t*0.02):.2f}" cy="{100 + 80*sin(t*0.02):.2f}" r="6" fill="brown"/>
</svg>
\```

渲染:太阳(橙)+ 地球(蓝)+ 火星(红)+ 木星(棕),各自轨道周期不同(地球转得最快,木星最慢)。timer 驱动所有行星同步运动。


Plain-text fallback behavior

\```interact
slider angle 0 360 0 1
template svg:
<svg viewBox="0 0 100 100">
  ...
</svg>
\```

→ 不支持的 reader 显示为代码块——读者看到完整 SVG markup(含 {angle} 占位符),有 SVG 知识的读者能想象画的是什么。

Reader 渲染效果
Rho 完整可交互 SVG 场景
GitHub web 代码块(SVG markup 字面量)
Obsidian 同上
VS Code 默认预览 同上
cat / less plain text

注:GitHub 等不会自动渲染 SVG markup(即使在 markdown 里直接写 <svg> 也不渲染)。Rho 的 svg template 才让它活起来。


Common pitfalls

1. SVG 语法错(缺 closing tag)

template svg:
<svg viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40"
                                    ← ❌ 缺 />
</svg>

→ SVG 解析失败,整块不渲染。SVG 是严格 XML——所有自闭合标签必须 />,所有元素必须配对 close。

推荐:在 SVG 编辑器(Inkscape / SVG-Edit / Boxy SVG)里画好导出 + 微调。

2. 占位符在错位置

template svg:
<circle cx={x} cy={y}>     ← ❌ 缺引号
<circle cx="{x}" cy="{y}">  ← ✅

SVG 属性值必须双引号包——即使是替换出数字。

3. mini DSL 不支持的函数

template svg:
<rect ... transform="rotate({Math.atan2(dy, dx)})">    ← ❌ Math.atan2 不存在
                              ← ✅ 用 atan2(dy, dx)(mini DSL 内置)

mini DSL 不是 JS——Math.X / Number.X 都不存在。直接用 Computed 函数列表。

4. 试图嵌入 HTML / script

template svg:
<svg>
  <foreignObject>...</foreignObject>     ← ❌ 被 sanitizer 剥离
  <script>...</script>                   ← ❌ 被剥离
  <a href="javascript:...">...</a>       ← ❌ href 被清空
</svg>

→ Rho 安全策略 故意阻止 这些。如果你需要这种能力,Rho 不是合适工具。

5. viewBox 没设 → 渲染异常

template svg:
<svg width="200" height="200">     ← ⚠️ 缺 viewBox,缩放行为不可预期
<svg viewBox="0 0 200 200" width="200" height="200">     ← ✅ 推荐

总是设 viewBox——它定义内部坐标系;外部 width/height 控制实际显示尺寸;二者协作才能可缩放。

6. 性能:too many elements

template svg:
<svg>
  <!-- 1000 个 circle 每帧重渲染 → 浏览器卡 -->
  <circle ... />
  <circle ... />
  ... × 1000
</svg>

→ SVG 元素数量直接影响渲染性能。< 200 元素最舒服。需要更多用 vega-lite 或 canvas(Rho 不支持后者)。

7. 动画跳变(mini DSL 表达式不连续)

template svg:
<circle cx="{floor(t*10)}" cy="50" r="3"/>     ← ⚠️ 用 floor 导致每秒只跳 10 次(看着卡)
<circle cx="{t*10:.2f}" cy="50" r="3"/>        ← ✅ 平滑插值

避免在表达式里用 floor / ceil / round(除非真的要离散效果)。


推荐 SVG 学习入口

如果你不熟 SVG:

熟练后回来,本页就够你做出 90% 的 Rho 动画。


See also