flowsvg

Declarative SVG player for logic and architecture diagrams, with scenes, beats, static export and browser framework adapters.
314
Install
npm install flowsvg
Latest Version:0.1.1
Vue:3
License:MIT
Last Updated:Oct 20, 2026
Links: npm
Maintainer: zac_ma

FlowSVG

English | 简体中文

FlowSVG(流形)用 SVG 播放逻辑图和架构图。用 JSON 或 TypeScript 定义节点、连线、场景与节拍,即可在浏览器播放或导出静态 SVG。

SDK 使用 ESM,提供无 DOM 核心及原生浏览器、Vue、React、Web Component 接入。各接入方式共用同一文档和时间线。

安装 · 快速开始 · Vue · React · 原生浏览器 · Web Component · API · License

安装

从 npm 安装:

pnpm add flowsvg

按所选入口安装框架依赖:

# Vue 应用
pnpm add vue
# React 应用
pnpm add react react-dom
# React TypeScript 应用
pnpm add -D @types/react @types/react-dom

核心与原生浏览器入口无需框架,类型声明随包提供。核心要求 Node.js 18+ 或 ES2022 ESM;浏览器要求 DOM、SVG 和 requestAnimationFrame;框架最低版本为 Vue 3.5、React 18。

导入路径 用途
flowsvg 无 DOM 的文档校验、编译、求值、命中与 SVG 导出
flowsvg/browser 原生浏览器播放器
flowsvg/vue Vue 组件与实例类型
flowsvg/react React 组件与 ref 类型
flowsvg/web-component 显式注册自定义元素;也可使用 flowsvg/element
flowsvg/styles.css 控件布局与可见焦点样式

快速开始

1. 创建图解文档

将下面内容保存为 diagram.ts。这个 5.2 秒的示例包含六个节点、三个场景和五个节拍,依次展示身份校验、订单写库,以及异步事件与响应的并行传递。后续集成示例复用此文件:

import type { DiagramDocument } from "flowsvg";

export const diagramDocument: DiagramDocument = {
  version: 1,
  title: "订单请求生命周期",
  viewport: { width: 960, height: 440 },
  nodes: [
    { id: "client", kind: "roundedRect", x: 40, y: 205, width: 120, height: 70, label: "Client" },
    { id: "gateway", kind: "roundedRect", x: 260, y: 205, width: 130, height: 70, label: "API gateway" },
    { id: "auth", kind: "ellipse", x: 260, y: 40, width: 130, height: 72, label: "Auth" },
    { id: "orders", kind: "roundedRect", x: 500, y: 205, width: 150, height: 70, label: "Order service" },
    { id: "database", kind: "cylinder", x: 780, y: 65, width: 140, height: 90, label: "Orders" },
    { id: "events", kind: "rect", x: 780, y: 330, width: 140, height: 70, label: "Event queue" }
  ],
  edges: [
    { id: "request", from: "client", to: "gateway", route: "straight" },
    { id: "authorize", from: "gateway", to: "auth", route: "straight", fromPort: "top", toPort: "bottom" },
    { id: "create", from: "gateway", to: "orders", route: "straight" },
    { id: "persist", from: "orders", to: "database", route: "orthogonal", fromPort: "right", toPort: "left" },
    { id: "publish", from: "orders", to: "events", route: "curved", fromPort: "bottom", toPort: "left" },
    { id: "result", from: "orders", to: "gateway", route: "curved", fromPort: "bottom", toPort: "bottom" }
  ],
  scenes: [
    { id: "request", label: "接收与认证", beats: [
      { durationMs: 800, caption: "接收订单请求", actions: [
        { type: "flow", target: "request", direction: "forward", text: "POST" }
      ] },
      { durationMs: 800, caption: "校验访问令牌", actions: [
        { type: "flow", target: "authorize", direction: "forward", text: "Token" }
      ] }
    ] },
    { id: "create-order", label: "创建订单", beats: [
      { durationMs: 1000, caption: "将已认证请求交给订单服务", actions: [
        { type: "flow", target: "create", direction: "forward", text: "Create" }
      ] },
      { durationMs: 1000, caption: "将新订单写入数据库", actions: [
        { type: "flow", target: "persist", direction: "forward", text: "INSERT" }
      ] }
    ] },
    { id: "response", label: "发布事件与响应", beats: [
      { durationMs: 1600, caption: "发布 OrderCreated 事件,同时返回订单号", actions: [
        { type: "flow", target: "publish", direction: "forward", text: "OrderCreated" },
        { type: "flow", target: "result", direction: "forward", text: "201 Created" },
        { type: "flow", target: "request", direction: "reverse", text: "Order ID" }
      ] }
    ] }
  ]
};

浅色主题下的 4400 ms 画面:事件与响应消息同时传递,仅高亮当前参与的节点。

订单流程包含 Client、API gateway、Auth、Order service、Orders 数据库和 Event queue,正在传递 OrderCreated 事件与响应消息

2. 求值并导出 SVG

import { createDiagram, renderSvg } from "flowsvg";
import { diagramDocument } from "./diagram.js";

const diagram = createDiagram(diagramDocument);
const frame = diagram.evaluateAt(125);
console.log(diagram.totalDuration, frame.sceneId, frame.warnings);

const svg = diagram.renderSvg(125, { theme: "dark" });
const finalSvg = renderSvg(diagramDocument, 5200);

两种调用均返回静态 SVG 字符串。重复查询时复用 createDiagram;单次导出用 renderSvg。

Vue 集成

在 Vue 应用中创建组件,diagram.ts 与组件放在同一目录:

<script setup lang="ts">
import { ref } from "vue";
import { FlowPlayer, type FlowPlayerHandle } from "flowsvg/vue";
import type { PlayerErrorDetail } from "flowsvg/browser";
import "flowsvg/styles.css";
import { diagramDocument } from "./diagram.js";

const player = ref<FlowPlayerHandle | null>(null);
const timeMs = ref(0);

function handleError({ error }: PlayerErrorDetail): void {
  console.error(error);
}
</script>

<template>
  <FlowPlayer
    ref="player"
    :document="diagramDocument"
    :speed="1.25"
    theme="dark"
    @timeupdate="timeMs = $event.timeMs"
    @error="handleError"
  />
  <button type="button" @click="player?.play()">播放</button>
  <button type="button" @click="player?.seek(0)">回到开头</button>
  <output>{{ timeMs }} ms</output>
</template>

卸载时自动销毁。脚本中通过 player.value 调用方法,模板自动解包 ref。事件直接传入 detail:stepchange、timeupdate、playstatechange、error,也支持对应回调 props。

React 集成

import { useRef } from "react";
import { FlowPlayer, type FlowPlayerHandle } from "flowsvg/react";
import "flowsvg/styles.css";
import { diagramDocument } from "./diagram.js";

export default function DiagramExample() {
  const player = useRef<FlowPlayerHandle | null>(null);

  return (
    <section>
      <FlowPlayer
        ref={player}
        document={diagramDocument}
        speed={1.25}
        theme="dark"
        onError={({ error }) => console.error(error)}
      />
      <button type="button" onClick={() => player.current?.play()}>播放</button>
      <button type="button" onClick={() => player.current?.seek(0)}>回到开头</button>
    </section>
  );
}

组件卸载时自动清理资源。回调为 onStepChange、onTimeUpdate、onPlayStateChange、onError。

Vue / React 的参数与更新行为

Prop 默认或含义
document 必填图解文档;对象引用变化时重新加载,成功后暂停在零时刻
speed 正数倍率,默认 1;变化时更新速度
theme light / dark;未指定时使用文档主题,最终默认为浅色
controls 默认 true,设为 false 隐藏内置控件
reducedMotion 未指定时跟随浏览器减少动画偏好
hoverFill 默认 false:悬停只高亮边框;设为 true 时同时高亮背景和内容
initialTimeMs 仅初次挂载时应用;后续跳转用 seek
className / style 宿主容器样式

修改 theme、controls、reducedMotion 或 hoverFill 会重建播放器,暂停在零时刻,不再应用 initialTimeMs。更新文档需提供新对象引用。实例暴露播放器 API中的七个方法;挂载前或卸载后调用会抛出错误。

两种入口均支持服务端导入。SSR 输出容器,客户端挂载后创建播放器。服务端图解预览使用 renderSvg。

原生浏览器

在页面中放置容器:

<div id="diagram" style="width: 100%; height: 360px;"></div>

在应用的客户端模块中创建播放器:

import { createPlayer } from "flowsvg/browser";
import "flowsvg/styles.css";
import { diagramDocument } from "./diagram.js";

const host = document.querySelector<HTMLElement>("#diagram");
if (!host) throw new Error("Diagram host is missing");

const player = createPlayer(host, diagramDocument, { theme: "light" });
player.play();

createPlayer 替换容器子元素。卸载或复用容器前调用 destroy()。原生 HTML 使用包导入路径时,需配置 import map 或构建工具。

原生示例演示请求与响应传递,同一文档也可用于其余三种接入方式。

Web Component

先在 HTML 中放置元素,再从客户端模块显式注册:

<flowsvg-player controls="false"></flowsvg-player>
import { defineFlowPlayer, type FlowPlayerElement } from "flowsvg/web-component";
import "flowsvg/styles.css";
import { diagramDocument } from "./diagram.js";

defineFlowPlayer();
const element = document.querySelector<FlowPlayerElement>("flowsvg-player");
if (element) element.document = diagramDocument;

默认注册 <flowsvg-player>,重复注册安全。对象赋给 element.document;JSON 字符串赋给 HTML document 属性;JSON URL 赋给 src。URL 相对页面解析,遵循浏览器 CORS 规则。

支持 autoplay、controls、speed、theme、reduced-motion、hover-fill。默认显示控件,关闭自动播放与悬停背景;controls="false" 隐藏控件。主题、控件及动画/悬停选项变化时保留时间和播放状态;autoplay 则直接控制播放或暂停。移除元素会取消请求并销毁播放器,重新连接时使用保留的文档。

方法:play、pause、seek、step、setSpeed、getState、destroy。更换文档使用 element.document。挂载前或销毁后,getState() 返回 undefined。播放与错误事件可冒泡并穿越 Shadow DOM 边界。

请在 DOM 环境注册。无 DOM 导入安全,但该模块实例不能在稍后补上 DOM 后用于注册。

文档与时间线

文档字段 约定
version 当前为 1
viewport 有限且为正的 width、height,定义初始视口
nodes 节点列表,id、kind、x、y、width、height 必填
edges 连线列表,id、from、to 必填;端点引用节点或分组边界
scenes [] 表示静态图;场景具有唯一 id、label 和非空 beats
title / theme 可选标题与 light / dark 文档主题

节点与连线 ID 全局唯一。形状:rect、roundedRect、ellipse、cylinder、group。parentId 指向父分组,子节点坐标相对分组原点。标签为纯文本,visible 默认 true。节点样式:fill、stroke、strokeWidth、textColor、dashed(分组边框);连线样式:stroke、strokeWidth、dashed。颜色使用十六进制 RGB/RGBA,如 #2563eb、#2563eb80。

路由:straight(默认)、orthogonal、curved。曲线跟随端口切线,不避让其他节点;正交路径避开可见普通节点,受阻时固定回退,不保证最短路径或零交叉。fromPort / toPort 可设为 top、right、bottom、left。显式正交端口保留向外线段,曲线端口决定切线。省略时按中心连线主方向选择,椭圆端点位于轮廓。普通标签按估算宽度换行;高度溢出时仍显示全文,并报告 text-overflow。

连线可连接节点或分组边界。分组端点跟随当前几何和显式端口;祖先隐藏时遮蔽连线。传递仅高亮端点分组,不联动子节点。分组边框不作为布线障碍。

固定节点内的节点内容

rect、roundedRect、cylinder 支持 content;椭圆和分组仅支持普通标签。content 与 label 互斥,空标签也算声明。content.blocks 按顺序排列 title、paragraph、list、graph 内容块。段落角色为 body(默认)或 auxiliary(辅助文字);空数组只显示形状。

import type { DiagramNode } from "flowsvg";

const service = {
  id: "service", kind: "rect", x: 24, y: 24, width: 260, height: 312,
  content: {
    style: { padding: 16, blockGap: 14 },
    blocks: [
      { type: "title", text: "Order service" },
      { type: "paragraph", role: "auxiliary", text: "Core responsibilities" },
      { type: "paragraph", text: "Validate and persist incoming orders." },
      { type: "paragraph", text: "Return the order number.", style: { color: "#2563eb", align: "right" } }
    ]
  }
} satisfies DiagramNode;

service 节点在浅色主题下的渲染结果:

固定尺寸的 Order service 节点,包含标题、辅助文字、正文与蓝色右对齐段落

文字字段接受字符串或 ContentTextSpan[]:{ text, style?: { fontSize, fontWeight, color } }。片段继承内容块样式并共用基线,对齐由内容块决定。每行使用最大字号;换行保留显式空行和完整 Unicode 字素,优先在英文空格处断行,必要时拆分长单词或 URL。跨片段的字素采用首字符样式。排版使用宽度估算,不测量 DOM。

文字样式:有限正数 fontSize、100–900 整数 fontWeight、十六进制 color、align(left、center、right)。内容的 padding、blockGap 为非负有限数。默认值按图解单位计:标题 20/700、正文 14/400、辅助文字 12/400、内边距 16、块间距 14;行高为 max(20, fontSize × 1.35)。显式颜色覆盖主题默认值。

列表接受 marker(默认 none,可选 bullet、number)、items 和 style。条目支持 tags、status、body、auxiliary、style、auxiliaryStyle、statusStyle。列表样式设置正文样式和非负有限数 itemGap(默认 12),条目正文样式可覆盖;辅助文字默认使用 12 单位弱化色。标记共用一列,与正文间隔 8 单位;正文续行与辅助文字对齐。标记对齐首个实际文字的基线。缺省条目/部分不占空间,显式空文字保留一行。

import type { ContentBlock } from "flowsvg";

const steps: ContentBlock = { type: "list", marker: "number", style: { itemGap: 16 }, items: [
  { body: "校验请求。", auxiliary: "输入检查" },
  { body: [{ text: "保存", style: { fontWeight: 700 } }, { text: "订单。" }] }
] };

将 steps 放入 300 × 180 的矩形,设置 content: { blocks: [steps] }:

包含辅助文字和加粗保存文字的两个编号步骤

文字标签接受 { text, style?: { fontSize, fontWeight, color, fill } }。默认字号 11,水平/垂直内边距 6/2,圆角 3。列表 tagGap 默认 6,须为非负有限数。状态靠右,默认使用 12 单位强调色;statusStyle 支持 fontSize、fontWeight、color。宽度足够时标签与状态共用基线;否则先换行标签,再将状态放到下方。正文、辅助文字随后排列。显式颜色和底色覆盖主题默认值。

import type { ContentBlock } from "flowsvg";

const records: ContentBlock = { type: "list", items: [{
  tags: [{ text: "world" }, { text: "experience", style: { fill: "#eadfff", color: "#57319b" } }],
  status: "new", body: "Alice joined Google", auxiliary: "Mar 2026"
}] };

将 records 放入 300 × 180 的矩形,设置 content: { blocks: [records] }:

记录包含 world 和 experience 标签、new 状态、正文与日期

内容保持节点原尺寸。安全区在内边距之外额外避让:圆角矩形每侧 4 单位,圆柱上下各两倍端盖半径(min(14, height / 4));宽高最小为零。SVG 按安全区裁剪,宽/高不足时报告带节点 ID、nodes[index].content 路径和方向的 text-overflow。裁剪文字不扩大命中范围,普通标签沿用独立的溢出规则。

EvaluatedNode.contentLayout 提供安全区、文字行/片段、标签底色、可选图形几何及宽高溢出标志。几何使用绝对坐标,跟随节点尺寸、父级移动、显隐和透明度。文字行保留块/条目/标签索引,explicitColor、explicitFill 标识作者配色。浏览器与静态 SVG 共用布局;内容不改变端口、路由、传递几何或父级关系。参见段落与列表示例。

节点内的实体关系小图

graph 内容块按作者指定的局部坐标绘制无向实体关系图:

import type { ContentGraphBlock } from "flowsvg";

const entities: ContentGraphBlock = {
  type: "graph", height: 130,
  nodes: [
    { id: "alice", label: "Alice", x: 120, y: 30, highlighted: true },
    { id: "google", label: "Google", x: 50, y: 110, highlighted: true },
    { id: "ml", label: "ML project", x: 190, y: 110 }
  ],
  edges: [{ from: "alice", to: "google" }, { from: "alice", to: "ml" }]
};

将 entities 放入 280 × 180 的矩形,设置 content: { blocks: [entities] }:

实体关系图中 Alice 和 Google 高亮,ML project 未选中

height 预留固定正数高度。实体中心 x/y 是相对区域左上角的非负有限坐标。ID 仅在块内唯一,关系端点引用块内 ID。12 单位标签位于半径为 4 的圆点上方。高亮实体使用强调色圆点与粗体;关系两端均高亮时使用强调色。溢出按方向报告,不移动后续内容块。图形共用父节点裁剪与主题,交互命中父节点。

contentLayout.graphs 按 blockIndex 提供绝对几何和高亮状态,标签进入 contentLayout.lines。在节拍起点替换 content 可切换选中状态。参见 Hindsight 示例。

编写完整 Facts 时间线

先确定尺寸,再排列内容块。记录用无标记列表,能力清单用圆点,步骤用编号。下例可用于全部四种接入方式:

import { createDiagram, type DiagramDocument, type NodeContent } from "flowsvg";

const facts: NodeContent = { blocks: [
  { type: "title", text: "Facts" },
  { type: "paragraph", role: "auxiliary", text: "已确认的信息记录" },
  { type: "list", marker: "none", items: [{
    tags: [{ text: "world" }, { text: "订单", style: { fill: "#eadfff", color: "#57319b" } }],
    status: "new",
    body: [{ text: "订单已保存", style: { fontWeight: 700 } }, { text: " 2026-10-17", style: { fontSize: 11 } }],
    auxiliary: "等待支付。"
  }] }
] };
const updated: NodeContent = { blocks: [
  { type: "title", text: "Facts" },
  { type: "list", items: [{ tags: [{ text: "verified" }], status: "done", body: "支付已确认", auxiliary: "2026-10-18" }] }
] };
const document: DiagramDocument = {
  version: 1, viewport: { width: 300, height: 480 },
  nodes: [{ id: "facts", kind: "cylinder", x: 24, y: 20, width: 240, height: 440, content: facts }], edges: [],
  scenes: [{ id: "records", label: "记录", beats: [
    { durationMs: 1000, actions: [] },
    { durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: updated } }] },
    { durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { label: "已归档" } }] },
    { durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: facts } }] },
    { durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: null } }] }
  ] }]
};
const diagram = createDiagram(document);
console.log(diagram.evaluateAt(1000).warnings);
const svg = diagram.renderSvg(1000, { theme: "dark", reducedMotion: true });

左图为 0 ms 的浅色主题初始画面,右图为 1000 ms 的深色主题替换后画面:

Facts 时间线在记录替换前后的浅色与深色主题画面

分享前检查 frame.warnings,按需增大节点或精简文字;SDK 不自动调整尺寸或缩小字号。优先使用默认颜色保持主题与高亮下的对比度。内容只有节点级交互,不支持内部链接、按钮、图片或 HTML。

内容时间线组合 Facts、列表、节点更新、传递与聚焦。共享文档也用于 Vue、React 示例。

每个节拍包含正数 durationMs 和 actions,可选 transitionMs 与 caption。过渡时长默认 min(250, durationMs),取值范围为 [0, durationMs];caption 省略时继承前文,空字符串可清空。

动作 示例或可更新字段
show / hide { type: "show", target: "service" },作用于节点、分组或连线
highlight / unhighlight 改变高亮层,取消高亮后显示最新普通样式
flow { type: "flow", target: "request", direction: "forward" };在一个节拍内让标记沿连线移动。direction 为 forward 或 reverse,不会改变静态箭头方向;添加 text: "请求" 显示水平文字胶囊,省略 text 显示实心圆点。
updateNode value 支持 x、y、width、height、label、content、fill、stroke、opacity
updateEdge value 支持 label、stroke、strokeWidth、opacity
focus target 为节点/分组 ID;null 恢复完整视口

updateNode.value.content 在节拍起点整体替换内容,不受过渡时长影响,结果继承到后续节拍与场景。label 切换到普通标签,content 切换到节点内容;content: null 清空任一模式,不恢复旧标签或改变尺寸。同一节拍同时赋值两种模式或重复赋值会报告字段路径错误。

// 每个 value 分别用于不同节拍的 updateNode 动作。
import type { NodeVisualState } from "flowsvg";

const replacement: NodeVisualState = { content: { blocks: [{ type: "title", text: "Updated" }] } };
const simpleLabel: NodeVisualState = { label: "Summary" };
const clearText: NodeVisualState = { content: null };

场景共用毫秒时间线,继承前一场景的最终状态。文字在节拍起点切换,插值属性在过渡期变化。节拍左闭右开,最终时间返回完成画面。隐藏节点/分组会遮蔽关联连线,重新显示时恢复各连线保存的显隐状态。

每拍可在多条不同连线上各传递一次。标记共用 durationMs 内的线性进度,因此路径越长,移动越快。标记和端点临时高亮在拍末消失,下一拍提供新的一组传递;显式高亮和普通样式继续继承。

传递 text 按纯文本转义,始终水平,胶囊宽度按固定规则估算。文字不替换连线标签,也不产生独立交互目标;空字符串仍显示胶囊。

API

核心 API

从 flowsvg 导入:

API 返回与行为
validateDocument(input: unknown) 返回 { path, message }[];空数组表示通过
createDiagram(document) 校验、复制并编译,返回 Diagram;非法文档抛出 DiagramValidationError
renderSvg(document, timeMs = 0, options?) 一次性编译并导出静态 SVG 字符串
diagram.totalDuration 全局总时长,静态文档为 0
diagram.evaluateAt(timeMs, options?) 返回 DiagramFrameState
diagram.renderSvg(timeMs = 0, options?) 导出相同求值结果的 SVG
diagram.hitTest(timeMs, point, options?) 在文档坐标中返回 { id, type: "node" | "edge" } 或 null
wrapText(label, maxWidth, typography?) 按固定字宽估算换行,返回文本行数组

EvaluateOptions:{ theme?: "light" | "dark"; reducedMotion?: boolean }。时间限制在 [0, totalDuration],非有限值按零处理。命中跳过隐藏/零透明度对象,依次检查普通节点、连线、分组背景。

DiagramFrameState 包含时间、场景/节拍 ID、进度、求值后的节点/连线、镜头、说明文字及警告。坐标为绝对坐标。静态图的场景/节拍 ID 为 null;节拍 ID 为 ${scene.id}:${beatIndex},索引从零开始。活动连线的 flow 提供方向、进度、位置及可选文字;拍末、隐藏或减少动画时不显示标记。

类型及成员 JSDoc 随包提供:DiagramDocument、DiagramNode、DiagramEdge、Scene、Beat、Action、Diagram、DiagramFrameState、EvaluateOptions、ValidationIssue、LayoutWarning。

播放器 API

从 flowsvg/browser 导入 createPlayer(host, document, options?)。PlayerOptions 包含核心视觉选项以及 controls?: boolean 和 hoverFill?: boolean。

方法或属性 行为
element 创建时传入的宿主元素,也是事件目标
play() / pause() 播放或保留当前画面并暂停;静态图或终点不会开始播放
seek(timeMs) 限制到时间线范围;保留播放状态,抵达终点时暂停
step(1) / step(-1) 暂停并跳到相邻节拍起点;节拍中途后退到本拍起点,在起点后退到上一拍
setSpeed(speed) 设置有限正数倍率,忽略非法值
load(document) 成功后暂停并回到零;校验失败时发出 error,保留原文档、时间和播放状态
getState() 返回 PlayerState:timeMs、totalDuration、playing、speed、frame
destroy() 幂等清理 DOM、监听器与调度;之后停止事件,状态仍可读取

创建时暂停在零时刻,默认显示控件。页面隐藏时暂停,恢复后保持暂停;播放在终点停止,静态图禁用时间控件。框架实例提供表中方法,但不暴露 element 和 destroy。

悬停形状、标签或内容时高亮当前节点边框。hoverFill: true 同时高亮背景与内容,也适用于分组。移出只解除悬停高亮,活动的传递/显式高亮仍保留。画面更新会重新检查未移动的指针;指针取消、文档成功替换和销毁会清除悬停。

默认内容颜色跟随主题与高亮底色,作者配色和标签底色保持原值。高亮不改变布局或命中范围。内容交互返回父节点,裁剪文字不增加命中区域。

PlayerState.frame 和静态 SVG 不含悬停;没有悬停时,浏览器输出与同时间导出一致。

事件

原生播放器在宿主元素上派发 CustomEvent:

事件 detail
timeupdate 当前 PlayerState,画面更新后发送
stepchange 当前 PlayerState,场景或节拍改变时发送,早于该次 timeupdate
playstatechange 当前 PlayerState,播放/暂停状态变化时发送
error PlayerErrorDetail:保留的播放器状态与 error
hover / click PlayerInteractionDetail 或 null;也提供 objecthover / objectclick 同义事件

交互 detail 包含 id、type、point(文档坐标)、timeMs、sceneId、beatId,未命中为 null。包装层转发四类播放/错误事件;Web Component 挂载前的错误可能没有播放器状态。

指针移动会发送 hover;画面更新导致未移动的指针命中其他对象时也发送。移出或取消时发送 null。

import type { PlayerEventMap } from "flowsvg/browser";

function handleTimeUpdate(event: Event): void {
  const { detail } = event as PlayerEventMap["timeupdate"];
  console.log(detail.timeMs, detail.frame.caption);
}

player.element.addEventListener("timeupdate", handleTimeUpdate);

上例沿用原生集成创建的 player。宿主自行添加的监听器由宿主移除。

主题与无障碍

主题:显式选项 → 文档主题 → 浅色,核心不读取系统偏好。浏览器动画默认跟随 prefers-reduced-motion,核心默认正常动画。减少动画会立即完成过渡并隐藏传递标记,保留总时长。

默认样式使用中性边框与连线,以蓝色突出活动对象:

颜色 浅色 深色
背景 / 节点填充 #ffffff / #ffffff #101828 / #1d2939
节点边框 / 文字 #cfd8e3 / #1d2939 #475467 / #f2f4f7
连线 #8892a4 #98a2b3
高亮填充 / 边框 #eaf1ff / #2a63c8 #233b60 / #8ab4f8
传递标记 / 标记文字 #2a63c8 / #ffffff #8ab4f8 / #101828

默认节点边宽为 1.2 图解单位,连线边宽为 2,圆角矩形的圆角半径上限为 8。节点标签使用无描边文字;连线标签保留背景描边,隔开穿过文字的路径。图解文字使用系统字体,并提供中文字体回退。显式样式替换普通默认值;时间线和悬停高亮叠加在普通样式上,解除高亮后恢复当前普通颜色。

导入 flowsvg/styles.css 可获得控件布局与焦点样式。CSS 变量只定制控件,图解本身颜色由核心主题和文档样式决定:

.flowsvg {
  --flowsvg-control-background: #101828;
  --flowsvg-control-color: #f4f7fb;
  --flowsvg-control-border: #64748b;
  --flowsvg-control-accent: #60a5fa;
  --flowsvg-control-focus: #fbbf24;
}

原生宿主添加 flowsvg 类即可应用变量,框架组件自动添加。画布聚焦后,Space 播放/暂停,方向键逐拍移动;表单控件保留原生按键。说明文字使用礼貌播报区,SVG 提供可访问名称。

校验与错误

校验拒绝未知字段、错误引用、循环分组、重复节拍赋值、非法颜色和尺寸/时长。文本按纯文本转义。检查外部 JSON 时,调用 validateDocument 并读取各条 path、message。

createDiagram 和首次 createPlayer 遇到非法文档会抛出 DiagramValidationError,issues 包含字段路径。后续 load 失败时发送 error,保留旧状态。文字溢出不阻止渲染,可读取 frame.warnings。

同一连线重复声明 flow 时,后一 target 报告 duplicate beat assignment,与方向、文字及其他字段错误无关。传递可与同一连线的显隐、显式高亮和样式动作共存。

License

MIT。可使用、修改及分发;保留许可证与版权声明。

Related Packages

@kokoa/clotho

JSON-defined visualization animations — framework-agnostic scene graph with Reac...

3,181 4
@dumbshow/vue

The Vue editor for dumbshow: ComposerApp and its panes over @dumbshow/core — mou...

883 0
@stew-engine/player

Plays a Stew export: the artwork, the scene data Stew wrote next to it and Stew'...

233
fluid-icons

Fluid Icons: 570+ soft, rounded, modern SVG icons. React, Vue, web component, ra...

165
blackout-animation

Animation modules for single page application.

478 1

Version History

Version Released License
0.1.1 MIT
0.1.0 MIT
Pre-releases (1)
Version Released License
0.0.0-stage