关于 framefields
framefields 是面向 Node.js 和 TypeScript 项目的代码优先视频 SDK 入门技能,可引导项目从初始化到完成首个验证渲染。它使用 WebGPU 原生渲染,无需浏览器。
主要功能
- 01初始化视频项目结构
- 02配置 SDK 与渲染脚本
- 03检查帧图和动画网格
- 04启动实时预览
- 05验证并导出视频
适用场景
- 01从零搭建 TypeScript 视频项目
- 02在完整导出前检查渲染帧
- 03通过预览审阅画面和声音
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ gatewai-dev/ framefields --skill framefields安装后在 Claude Code 中输入/framefields确认已加载。
详细介绍
framefields
概述
framefields 是一个代码优先的视频 SDK。视频是一个 TypeScript Composition(由带关键帧动画的图层构成的树),可在 Node.js 中通过 WebGPU(Dawn)原生渲染,无需浏览器、Puppeteer 或 Chromium。引擎是 npm 包 framefields;以下所有操作都在用户自己的项目中运行。
本技能将项目从零开始带到经过验证的首次渲染。除此之外的任务,请加载相应的专业技能:
| 任务 | 技能 |
|---|---|
| 场景、布局、排版、动画、节拍网格、影片结构 | framefields-compose |
| 后期处理、调色、视觉特效、设备端视觉(追踪、抠像、姿势) | framefields-effects |
| 帧检查、帧网格、像素测试、视频导出与质量保证 | framefields-render |
现成组件(npx framefields add) | 本技能,第 8 节 |
使用 AI 模型生成图像、视频、音乐、语音(ai) | 本技能,第 6 节 |
| 自主 Agent 付款与运行时间检查 | 本技能,第 7 节 |
| 向 GitHub 报告 Bug 或引擎缺陷 | 本技能,第 9 节 |
1. 检查环境
- **Node.js 22 或更高版本。**运行
node --version。较早版本会在导入时失败。 - **GPU 或 CPU 渲染器。**macOS 使用 Metal(所有 Mac 和 macOS 虚拟机都有 Metal GPU)。Linux 使用 Vulkan,Windows 使用 Direct3D 12 或 Vulkan。没有 GPU 时,framefields 会在 CPU 上渲染:Linux(沙箱、CI 运行器或容器)通过 Mesa lavapipe 渲染,可使用
apt-get install -y libvulkan1 mesa-vulkan-drivers安装;Windows 则通过 WARP(Windows 内置)或 lavapipe 渲染。所有功能在此环境下的渲染结果相同(文本、媒体、Lottie、特效、3D、视觉),只是速度较慢:在我们的 Linux 测试中,比 M1 Pro 的 GPU 慢约 12 倍。describeGpu()会报告software: true。适合渲染帧、网格和较短的导出;长影片请使用 GPU 机器或渲染工作器(framefields-render)。 - **ES 模块项目。**项目的
package.json需要包含"type": "module"。
2. 安装
最快的方式:npx framefields init。 它会为你生成第 2 节和第 3 节中的所有内容:package.json(包含下方的安装脚本审批、脚本、依赖项和 packageManager)、tsconfig.json、src/theme.ts / film.ts / render.ts、assets/、LFS .gitattributes 和 .gitignore。它还会下载 Inter 和 JetBrains Mono(固定版本,并附带其 OFL 许可证),将它们注册到生成的 src/components/fonts.ts 中,供 theme.ts 调用,并运行 git init。
npx framefields init my-film --install # a new project; or `init` inside an existing one
在现有项目中,它绝不会覆盖文件:已有文件保持不变,package.json、.gitignore 和 .gitattributes 只会补上缺少的内容(如果 "type": "commonjs",则会拒绝执行)。选项:--pm npm|pnpm|yarn|bun(默认使用当前运行它的包管理器)、--emoji(同时添加彩色表情符号字体)、--no-fonts、--no-git、--name。它不会询问任何问题,因此可在 CI 中无人值守运行。AI 网关密钥是可选的:init 会写入 .env.example 并将 .env 添加到 Git 忽略列表;render.ts 会在 .env 存在时加载它;用户自行在其中设置 FRAMEFIELDS_API_KEY(绝不要替用户写入密钥)。然后转到第 4 节。本节其余部分和第 3 节介绍它会生成的内容,供手动操作或合并到不常见的项目配置中时参考。
使用项目当前采用的包管理器(查看是否存在 pnpm-lock.yaml、yarn.lock、bun.lock 或 package-lock.json)。
安装前先写入 package.json。 npm 11、pnpm 10 和 bun 默认会阻止依赖项安装脚本,除非项目批准这些脚本;而 framefields 只需要批准一个:skia-canvas 会在其安装脚本中下载原生二进制文件。否则,import "framefields" 会报错:Cannot find module '.../skia.node'。其他带有安装脚本的软件包无需批准也能正常工作,因此请拒绝它们的脚本,以保持安装过程安静,并跳过 framefields 不会用到的下载。
对于新项目,创建包含以下三个区块的 package.json。对于现有项目,将它们合并到其 package.json 中,并保留已有条目:
{
"name": "my-film",
"private": true,
"type": "module",
"allowScripts": {
"skia-canvas": true,
"sharp": false,
"webgpu": false,
"onnxruntime-node": false,
"node-av": false,
"esbuild": false,
"protobufjs": false
},
"pnpm": {
"onlyBuiltDependencies": ["skia-canvas"],
"ignoredBuiltDependencies": ["sharp", "webgpu", "onnxruntime-node", "node-av", "esbuild", "protobufjs"]
},
"trustedDependencies": ["skia-canvas"]
}
npm 读取 allowScripts,pnpm 读取 pnpm,bun 读取 trustedDependencies;每个工具都会忽略其他工具的字段。Yarn 默认运行安装脚本,因此不需要这些字段。还要添加 "packageManager",其值应为你安装时所用包管理器及版本的准确名称(例如 "[email protected]"),并提交安装生成的锁文件(pnpm、npm 或 yarn;bun 锁文件无法由工作器渲染)。然后安装:
npm install framefields
npm install --save-dev tsx typescript @types/node
安装必须在没有 install-scripts 警告的情况下完成。如果在审批生效前就安装了 framefields,请添加审批并使用 npm rebuild skia-canvas(pnpm rebuild skia-canvas)重新运行被跳过的脚本。切勿一次性批准所有脚本(npm install-scripts approve --all)。
最精简的 tsconfig.json:
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"types": ["node"]
},
"include": ["src/**/*"]
}
3. 搭建项目骨架
将构图与渲染它的脚本分开,这样测试、预览、渲染脚本和渲染工作器都会构建同一部影片。film.ts 必须只负责构建:不得渲染、调用 process.exit(),也不得在导入时写入文件。
src/
theme.ts # canvas size, fps, colors, fonts: the only place for magic numbers
film.ts # buildFilm(): assembles the Composition
render.ts # renders frames for review, a live preview, or the full video
assets/
fonts/ # .ttf / .otf files the film uses
footage/ # source videos (.mp4, .mov) and audio (.mp3, .wav) tracked in Git LFS
output/ # renders (add to .gitignore)
文本需要真实的字体文件。询问用户要使用哪种字体。
// src/theme.ts
import { FontManager } from "framefields";
export const W = 1920;
export const H = 1080;
export const FPS = 30;
export const INK = "#0E0D0C";
export const PAPER = "#EEE9E0";
export const SANS = "Inter";
export async function registerFonts(): Promise<void> {
await FontManager.register({ family: SANS, source: "assets/fonts/Inter.ttf" });
}
// src/film.ts
import { Composition, Layer, LayerAnimation } from "framefields";
import { FPS, H, INK, PAPER, registerFonts, SANS, W } from "./theme.js";
const DURATION = 3 * FPS; // frames
export async function buildFilm(): Promise<Composition> {
// Fonts must be registered before any text layer is built.
await registerFonts();
const film = new Composition({
width: W,
height: H,
fps: FPS,
durationMs: (DURATION / FPS) * 1000,
backgroundColor: INK,
});
film.add(
Layer.text("Hello, framefields", {
id: "title",
position: "absolute",
x: 0,
y: H / 2 - 60,
width: W,
height: 120,
fontFamily: SANS,
fontSize: 96,
fontWeight: 700,
fill: PAPER,
align: "center",
}).animate(
LayerAnimation.create()
.fadeIn(0, 20, "power2.out")
.fromTo("y", H / 2 - 20, H / 2 - 60, { start: 0, end: 30, ease: "expo.out" }),
),
);
return film;
}
// src/render.ts
import fs from "node:fs/promises";
import path from "node:path";
import { HeadlessMediaRenderer, startPreview } from "framefields";
import { buildFilm } from "./film.js";
const OUT = path.resolve(import.meta.dirname, "../output");
const [mode = "video", ...args] = process.argv.slice(2);
if (mode === "preview") {
// The page runs film.ts itself and renders it with WebGPU in the browser.
const session = await startPreview({ entry: new URL("./film.ts", import.meta.url), export: "buildFilm" });
console.log(`Preview at ${session.url}`);
await session.closed; // until its tab closes or the next preview replaces it
process.exit(0);
}
const film = await buildFilm();
await fs.mkdir(OUT, { recursive: true });
if (mode === "frames") {
const renderer = new HeadlessMediaRenderer();
for (const frame of args.map(Number)) {
const png = await film.renderFrame({ frame, renderer });
await fs.writeFile(path.join(OUT, `f${String(frame).padStart(4, "0")}.png`), png);
}
} else if (mode === "grid") {
const png = await film.renderFrameGrid({
frames: args.length ? args.map(Number) : [0, 10, 20, 30, 45, 60, 75, 89],
columns: 4,
cellWidth: 480,
showLabels: true,
});
await fs.writeFile(path.join(OUT, "grid.png"), png);
} else {
const result = await film.renderVideo({
outputPath: path.join(OUT, "output.mp4"),
quality: "high",
});
await result.cleanup?.();
console.log(`Exported ${result.filePath}`);
}
// Native GPU and encoder threads keep Node alive; always exit explicitly.
process.exit(0);
在 package.json 中添加脚本:
"scripts": {
"typecheck": "tsc --noEmit -p .",
"frames": "tsx src/render.ts frames",
"grid": "tsx src/render.ts grid",
"preview": "tsx src/render.ts preview",
"render": "tsx src/render.ts"
}
视频素材与 Git LFS
视频输入(Layer.video("assets/footage/clip.mp4"))、Lottie 动画(Layer.lottie(...))、音轨(Layer.audio(...))、LUT 和自定义字体应纳入 Git LFS 管理,而 output/ 中的渲染输出应加入 .gitignore。
-
初始化 Git LFS:
git lfs install -
在项目根目录配置
.gitattributes:assets/**/*.mp4 filter=lfs diff=lfs merge=lfs -text assets/**/*.mov filter=lfs diff=lfs merge=lfs -text assets/**/*.webm filter=lfs diff=lfs merge=lfs -text assets/**/*.mp3 filter=lfs diff=lfs merge=lfs -text assets/**/*.wav filter=lfs diff=lfs merge=lfs -text assets/**/*.cube filter=lfs diff=lfs merge=lfs -text assets/**/*.lottie filter=lfs diff=lfs merge=lfs -text assets/fonts/**/*.ttf filter=lfs diff=lfs merge=lfs -text assets/fonts/**/*.otf filter=lfs diff=lfs merge=lfs -text -
渲染前还原媒体文件: 在刚克隆或容器化的 Agent 环境中,LFS 媒体会以约 130 字节的文本指针文件形式存在。Framefields 原生的 WebGPU 和 WebCodecs 解复用器无法解码这些指针,因此会失败或生成空白帧。务必确保已还原媒体文件:
git lfs pull -
让
.gitignore仅针对需要忽略的内容: 使用 LFS 跟踪源素材时,切勿将*.mp4加入.gitignore。应忽略输出目录(output/、scratch/)。
4. 验证,先做成本最低的步骤
切勿直接进行完整的视频渲染。在没有 GPU 的机器上,所有渲染都会在 CPU 上运行(lavapipe 或 WARP),因此这些低成本步骤尤为重要。每个步骤都能在几秒内发现问题,而视频渲染则需要几分钟才能显示问题:
npm run typechecknpm run frames -- 0 30 60,查看output/中的 PNG。黑帧通常意味着缺少字体、图层位于画布之外,或不透明度卡在 0。npm run grid,检查动作在联系表中是否流畅。- 有人正在观看时运行
npm run preview:导出前先播放影片并检查声音(见下文)。 - 确认帧和动作效果正常后,运行
npm run render。在后台运行,并设置较长的超时时间。它会记录每个阶段(Rendered 40% …、Mixing audio、Muxing mp4、Done in …),如果某一步运行时间较长,还会输出一行Still …。因此,最后一帧渲染完成后的停顿是在混合音频或封装,而非程序卡住。
完整渲染前,先向用户展示帧 PNG 或网格。framefields-render 涵盖像素探测测试、渲染 QA(renderVideo({ qa }))以及空白帧诊断。如果怀疑引擎本身或 ai gateway 存在问题或缺陷,请参阅第 9 节。
实时预览
npm run preview 会启动一个本地主机播放器(画面、声音、时间轴),并输出其 URL。页面会自行运行 film.ts,并使用 WebGPU 实时渲染,因此用户看到的是真实影片;速度较慢的设备会跳过帧,以跟上声音。
- 将其作为后台命令运行并读取输出:输出会说明页面何时打开、打印页面中的错误(构建失败、某帧抛出异常、缺少字体),并说明停止原因。最后一个标签页关闭 5 s 后,预览会停止;再次运行即可。
- 它不会打开任何内容。如果你的应用内置了浏览器,请在那里打开 URL;否则将链接提供给用户。
startPreview(..., { open: true })会打开用户的默认浏览器。 - 根据文件的 URL 引用资源。 页面会自行运行
film.ts,因此资源路径也必须能在浏览器中解析:根据模块自身的 URL 构建路径,例如path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../assets/fonts"),不要使用process.cwd()或裸相对路径。预览会提供项目(包含package.json的文件夹)中的所有文件;对于保存在其他位置的字体或媒体文件,请将一个或多个路径传给startPreview的root。 - 修改影片后,请重新运行。它会接管同一个 URL,已打开的标签页也会在同一时刻自动重新加载。
- 使用
#t=12.5(秒)链接到某个时刻。在可编写脚本的浏览器中,window.framefieldsPreview提供seek(seconds)、play()、pause()和state();seek会在帧绘制完成后解析,因此请在此后截图。 - 用户可以在画面上固定备注。 暂停后点击(或拖动框选区域),即可在相应位置和时刻添加备注。每条备注都会显示在预览输出中,并保存到
.framefields/preview-notes/:notes.json,以及标记了备注位置的画面note-<id>.jpg。用户表示已留下备注时,请读取notes.json,并在编辑前查看每条未解决备注的图片。然后重新运行预览,让用户检查并勾选已处理的备注。详情请参阅framefields-render。
5. 在服务器上渲染
framefields 渲染工作器(Docker、RunPod pod 或无服务器环境)会渲染已推送的提交:拉取 LFS 资源、根据锁文件安装依赖、导入 src/film.ts、调用 buildFilm,并上传结果。按上述方式构建的项目无需修改即可使用,但须满足以下条件:
framefields版本为 2.2 或更高,锁文件已提交且与package.json匹配,并且packageManager已固定版本;- 所有输入都位于
assets/下,由 Git LFS 跟踪并已推送(git lfs ls-files会列出这些文件); - 资源路径来自
import.meta.url,绝不使用process.cwd(); - 渲染期间没有任何内容写入
node_modules(文件以只读方式共享)。
作业失败时会返回 { code, hint, retryable };仅当 retryable 为 true 时,才可原样重新提交。作业格式和错误代码:https://framefields.dev/docs/guides/render-service/
6. 使用 AI 生成资源
如可用,请使用已连接的 Framefields MCP 服务调用 list_models、get_model_schema 和 estimate_generation_cost(在 Claude Code 会话中,这些工具以 mcp__plugin_framefields_framefields__list_models、mcp__plugin_framefields_framefields__get_model_schema 和 mcp__plugin_framefields_framefields__estimate_generation_cost 的形式提供)。使用本地脚本中的 import { ai } from "framefields" 来生成、编辑或处理媒体;MCP 不会提交或检索生成作业。SDK 使用单独的网关 API 密钥(FRAMEFIELDS_API_KEY)进行身份验证,而非 MCP OAuth 连接。仅使用当前支持的目录模型,以及所选模型架构中的输入字段。
list_models:查看目录模型及其类别任务(text-to-image、text-to-video、speech-to-text等)。get_model_schema:编写脚本前,检查模型所需和可选的输入。estimate_generation_cost:运行付费生成前,查看已公布的费率或请求费用估算。framefields通过 Framefields 网关导出用于图像、视频和音频操作的ai。每个请求都使用模型 ID 和账户额度。run、subscribe、stream、queue和storage支持网关工作流。
import { ai } from "framefields";
const apiKey = process.env.FRAMEFIELDS_API_KEY;
if (!apiKey) throw new Error("Set FRAMEFIELDS_API_KEY before generating assets.");
ai.config({ apiKey });
const { data, requestId } = await ai.subscribe<{ images: { url: string }[] }>("seedream/4.5", {
input: { prompt: "Neon street at night, rain, 35mm", image_size: "landscape_16_9" },
});
console.log(data.images[0].url);
密钥。 请求使用从 https://framefields.dev/dashboard/keys 获取的 ff_live_… 密钥进行身份验证。对于本地执行,请让用户自行在进程环境中设置 FRAMEFIELDS_API_KEY;绝不要让他们把密钥粘贴到聊天中。脚本会读取 process.env.FRAMEFIELDS_API_KEY,如果缺少该变量,则会显示可执行的提示信息并失败。不要检查或打印环境变量或凭据文件,不要将密钥嵌入源代码,也不要提交密钥。AI 生成是可选的,不会影响本地合成和渲染。没有密钥时,无法调用网关。失败时会抛出 ApiError,其中包含网关的 type:遇到 insufficient_credits(状态码 402)时,告知用户前往 https://framefields.dev/dashboard/billing 充值,而不是重试;key_limit_reached 表示该密钥已达到月度上限;rate_limited 和 too_many_inflight_requests 表示需要等待后重试。ValidationError(422)会在 fieldErrors 中列出输入错误的字段。
在独立脚本中生成一次并保存到 assets/。 生成需要付费,且结果并非确定,因此绝不会在 buildFilm()、预览、测试或渲染工作进程中运行。编写 src/generate.ts,按需运行,将所有输出下载到 assets/generated/(如上所述,通过 Git LFS 跟踪),并在影片中引用本地文件。生成的输出 URL 会过期,工作进程会从代码库渲染。
// src/generate.ts
import fs from "node:fs/promises";
import path from "node:path";
import { ai } from "framefields";
const apiKey = process.env.FRAMEFIELDS_API_KEY;
if (!apiKey) throw new Error("Set FRAMEFIELDS_API_KEY before generating assets.");
ai.config({ apiKey });
const OUT = path.resolve(import.meta.dirname, "../assets/generated");
await fs.mkdir(OUT, { recursive: true });
async function save(url: string, name: string): Promise<string> {
const file = path.join(OUT, name);
await fs.writeFile(file, Buffer.from(await (await fetch(url)).arrayBuffer()));
return file;
}
const still = await ai.subscribe<{ images: { url: string }[] }>("seedream/4.5", {
input: { prompt: "Neon street at night, rain, 35mm", image_size: "landscape_16_9", seed: 7 },
});
await save(still.data.images[0].url, "street.jpg");
// Feed a local file to a model: put a Blob in the input and it is uploaded for you
// (or call ai.storage.upload(blob) yourself and pass the URL).
const image_url = new Blob([await fs.readFile(path.join(OUT, "street.jpg"))], { type: "image/jpeg" });
const clip = await ai.subscribe<{ video: { url: string } }>("seedance/2.0/image-to-video", {
input: { prompt: "slow push in, rain falling", image_url, duration: "5" },
logs: true,
onQueueUpdate: (u) => u.status === "IN_PROGRESS" && u.logs.forEach((l) => console.log(l.message)),
});
await save(clip.data.video.url, "street.mp4");
process.exit(0);
然后在影片中使用 Layer.image("assets/generated/street.jpg") / Layer.video("assets/generated/street.mp4")。
- 从目录中选择模型及其输入。 https://framefields.dev/models 列出了每个模型的 ID、类别、单价和输入架构。每个模型都有自己的输入和输出字段(
images[0].url、video.url、audio.url、audio_file.url等):阅读架构,并将输出形状作为类型参数传入,或记录一次data,不要凭猜测。 - 长时间运行的任务(视频、音乐):
subscribe会等待并轮询。若要同时处理多个任务,分别调用ai.queue.submit(model, { input }),保存每个request_id,然后调用ai.queue.result(model, { requestId });如果脚本中断,可以收集结果,而不必再次付费。读取状态或结果是免费的,并会在临时错误时重试;提交操作绝不会重试,因此不会重复扣费。 - **费用:**生成会消耗额度。需要时,可使用 MCP 价格估算工具或模型目录报告费用估算或单价;实际收费可能有所不同。如果只知道单价,不要虚构总费用。
- 模型支持时传入固定的
seed,并将提示词保存在generate.ts中,以便重新生成结果。 - 帧率和尺寸:生成的片段很少与合成的
fps或宽高比相符。在图层上使用fit: "cover",并在剪辑前检查片段的实际时长。
7. 自主 Agent 付款
与 AI 网关交互的编码 Agent 可以发现端点、检查余额可用时长,并以编程方式处理充值:
- **清单发现:**读取
GET https://framefields.dev/.well-known/agent-payment.json,获取实时身份验证方案、套餐金额和端点路径。 - **余额可用时长检查:**使用进程环境变量
FRAMEFIELDS_API_KEY作为 bearer 凭据(切勿打印该值)调用GET https://framefields.dev/api/v1/agent/balance,即可返回{ balance_usd, held_usd, available_usd }。在执行耗费较大的生成批次前查询此接口。 - **自主处理 402 流程:**当额度用尽时,网关会返回 HTTP
402 Payment Required,并附上动态 Dodo 结账链接:
在聊天或终端中将 URL 提供给用户,以便通过 Dodo Payments 授权付款。{ "status": "payment_required", "action_required": { "type": "redirect_to_checkout", "url": "https://checkout.dodopayments.com/buy/...", "instructions": "Pass this URL to your human user to complete authorization." } } - **按需充值:**也可以调用
POST https://framefields.dev/api/v1/agent/top-up,并传入{"amount": 25}(或 10、50、100),以动态生成结账会话。
8. 拓展影片
首次渲染成功后,继续使用专业技能:用 framefields-compose 按节拍网格将影片拆分为场景模块,用 framefields-effects 进行调色、添加颗粒和视觉驱动效果,并用 framefields-render 执行测试和交付检查。随着项目不断扩展,请将 theme.ts 保持为尺寸、颜色、字体和时间安排的唯一来源。
现成组件
framefields 有一个参数化动态组件注册表:包含带点击和拖动效果的鼠标指针、缩放至点击位置的摄像机、窗口和设备边框、按单词计时的字幕、文字逐步显示、计数器、注释、终端、代码块、标题卡片、下三分之一字幕、仪表、聊天串、提示框、标注、聚光灯、彩纸、Logo 显示、背景、音频波形、单词轮播、触摸效果、前后对比拆分、选区和卡片堆叠。它们不在 npm 包中:framefields add 会将其源代码复制到项目中,之后它们就是普通的项目代码,可以阅读和修改。
npx framefields list # every component, one line each
npx framefields list --json # generated props, dependencies, fonts and examples
npx framefields fonts list --json # pinned font catalog and configured paths
npx framefields api # generated timing/grid/preflight reference
npx framefields add mouse-pointer focus-zoom # writes src/components/<name>/
npx framefields fonts add emoji # color emoji (chat-thread and captions add it themselves)
生成的项目还包含 check(TypeScript 加上 schema/lint/时间线预检)和 check:render(额外执行 GPU 帧/网格冒烟检查)脚本。这些脚本会生成带版本信息的 JSON,供 Agent 使用,并在发生错误时以失败代码退出。对于自定义入口,可使用 framefields check src/film.ts --export buildFilm --json;添加 --render 可将检查用产物写入 output/check。请确保构建器不执行生成、渲染和写入操作,因为预检会导入并执行它。
import { MousePointer } from "./components/mouse-pointer/index.js";
const pointer = MousePointer({
path: [
{ to: [400, 300], at: 0 },
{ to: [960, 540], at: 30, click: true },
],
});
film.add(pointer);
// pointer.cues.clicks[0].frame: key a button press or a sound to the click.
- 手写常见动态效果前,请先运行
npx framefields list。 组件已处理好计时、主题、提示和边缘情况,复制到项目中的代码可由项目自行调整。 - 文件会放入
src/components(如果没有src,则放入components);该文件夹会记录在framefields.json中,首次使用时可通过--dir修改。依赖其他组件的组件会一并添加它们(terminal会添加app-frame和counter)。 - 已安装的代码只导入
framefields和framefields/components(共享工具包),因此始终与已安装的 framefields 版本一致。升级 framefields 后,运行npx framefields add <name> --overwrite可刷新组件;不加该标志时,已修改的文件会保持不变,并报告为skipped。 - 需要使用字体的组件会附带字体。
chat-thread和captions需要彩色表情符号字体。add会将 Noto Color Emoji(SHA-256 固定版本,约 10.7 MB,随附 OFL 许可证)下载到assets/fonts/,在framefields.json的fonts下记录该字体,并写入src/components/fonts.ts。请在构建或渲染前调用一次其registerFonts(),与FontManager.register调用放在一起。--no-fonts会跳过下载;npx framefields fonts add emoji可稍后安装该字体(也可用于普通的Layer.text);若将framefields.json中的fonts.emoji指向其他字体,则会保留你自己的字体。请使用 Git LFS 跟踪assets/fonts/**。 - 没有表情符号字体时,表情符号不会显示。
comp.lint()会报告missing_emoji_font,首次渲染时也会打印相同的警告。请使用上面的命令解决,不要删除表情符号。 - 每个组件都接受基础属性(
x、y、width、height、at、duration、hold、theme、enter、exit、seed、fps、canvas),并返回一个普通的盒状图层,其中包含cues(事件发生的帧)、span和parts(可设置动画或样式的内部图层)。时间以帧为单位。 - 每个已安装的
index.ts都会在文件顶部说明组件的功能,并在其 schema 中定义属性;npx framefields list会提供单行摘要。构建组件时会验证属性,错误属性会抛出异常并说明原因。
9. 报告引擎问题
遇到意外崩溃、原生 WebGPU / Dawn 故障、WGSL 着色器编译错误、合成器架构验证缺陷,或 framefields 本身的行为错误(可与项目级配置或资源错误区分)时:
- 先询问用户: 未经明确确认,绝不自行创建问题。说明发生了什么,总结复现步骤,并询问用户是否希望向
gatewai-dev/framefields报告问题。 - 收集诊断详情:
- 操作系统和架构(
process.platform、process.arch)。 - Node.js 版本(
node --version)和framefields版本。 - GPU 后端和设备(运行
describeGpu(),或注明 Metal、Vulkan、DX12 或 CPU lavapipe/WARP)。 - 最小复现代码(触发问题的最小合成内容或脚本)。
- 完整错误消息、堆栈跟踪或验证失败日志。
- 操作系统和架构(
- 创建问题: 用户确认后,通过 GitHub CLI 提交问题:
如果环境中没有gh issue create --repo gatewai-dev/framefields --title "<concise summary>" --body "<diagnostic details and repro>"gh,或尚未通过身份验证,则输出标题、格式化后的正文,以及供用户使用的直接链接:https://github.com/gatewai-dev/framefields/issues/new。