关于 composition
使用 Editframe Web 组件或 React 组件构建视频合成的 Skill,提供统一时间线模型来编排场景、媒体、时长、转场和渲染。
主要功能
- 01支持 Web 组件与 React 语法
- 02场景排序与时长模式
- 03视频、音频、图像和文本元素
- 04与时间线同步的动画
- 05通过浏览器或 API 导出视频
适用场景
- 01用视频片段编排场景
- 02添加旁白、音乐和音效
- 03制作定时文本与语音字幕
- 04将合成内容导出为视频
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ editframe/ skills --skill composition安装后在 Claude Code 中输入/composition确认已加载。
详细介绍
视频合成
使用 Editframe 元素构建视频场景。可以使用 Web 组件,例如 <ef-timegroup> 和 <ef-video>;也可以使用 React 组件,例如 <Timegroup> 和 <Video>。两种语法共用同一套合成模型和渲染流程。选择适合你项目的语法即可。
Web 组件属性使用 kebab-case,例如 file-id 和 api-host。React 属性使用 camelCase,例如 fileId 和 apiHost。有四个属性不遵循此规则:sourcein、sourceout、trimstart 和 trimend。这些属性在 HTML 属性和 React 属性中均使用相同的小写字符串。元素参考部分列出了每个属性的两种形式。
让 Editframe 控制时间和媒体
Editframe 拥有合成时钟和所有媒体源。使用 Editframe 元素和属性构建时间安排和媒体。使用 CSS 控制布局和外观。使用 JavaScript 在 Editframe 帧钩子中计算视觉效果。
| 需求 | Editframe 工具 |
|---|---|
| 场景、场景顺序和剪辑点 | 使用 mode="sequence" 的 ef-timegroup |
| 场景时长 | 使用 contain 根据媒体确定时长;使用带 duration 的 fixed 设置编辑节拍;使用 fit 适配父级时长 |
| 场景内的开始时间 | 子元素上的 offset |
| 转场 | 序列上的 overlap,以及 --ef-transition-duration 和 --ef-transition-out-start |
| 视频片段和裁剪 | 使用带有 sourcein、sourceout、trimstart 和 trimend 的 ef-video |
| 旁白、音乐和音效提示 | 使用带有 offset、volume 以及相同裁剪属性的 ef-audio |
| 图像 | ef-image |
| 错落显示的文本 | 使用带有 split 和 stagger 的 ef-text |
| 语音字幕 | 使用包含按词计时的转录数据的 ef-captions |
| 音频响应视觉效果 | 使用带有 target 的 ef-waveform |
| 随进度变化的样式 | --ef-progress 和 --ef-duration |
| 每帧的计算状态 | addFrameTask、React onFrame 或 useTimingInfo() |
| 视频输出 | 从浏览器编辑器导出、使用 renderTimegroupToVideo(),或使用 ef-workbench 的 exportVideo() 方法 |
Editframe 还会控制合成中的所有 CSS 动画和 element.animate() 动画。它会暂停每个动画,并将其定位到对应时间元素的局部时间。该时间元素是动画元素本身,或离它最近的时间祖先。animation-delay 从该元素开始计时,而不是从页面加载时开始计时。请将每个动画放在其时钟所对应的时间组中。
不要使用通用浏览器工具重建这些系统:
- 不要使用
<video>或<audio>元素。Editframe 不会定位它们。导出时只会混合来自ef-video和ef-audio的声音。 - 不要使用
setTimeout、setInterval、requestAnimationFrame或墙上时钟来计时事件。这些计时器不会响应定位操作,也不会遵循渲染时钟。 - 不要在组件渲染或帧任务运行时调用
Math.random()。导出时会在离屏克隆中定位每一帧,而且某些渲染会在时间线中途启动新文档。因此,这些值会在预览、渲染结果以及渲染的各个部分之间发生变化。请根据索引或提供的时间计算变化值。对于随机值,请在模块作用域中调用一次具有固定种子的生成器。不要使用Date、performance.now()或crypto生成可见内容,因为它们无法保证确定性。 - 不要运行第二个动画时钟,例如会自行播放的动画库时间线。创建暂停状态的库动画,并通过
addFrameTask将其定位到提供的时间:使用{ paused: true }构建 GSAP 时间线,并以秒为单位调用seek(ownCurrentTime);创建autoplay: false的 anime.js 动画,并以毫秒为单位调用seek(ownCurrentTime * 1000)。在帧任务中使用renderer.render()渲染 Three.js 场景,创建WebGLRenderer时设置preserveDrawingBuffer: true,并且不要调用setAnimationLoop()。 - 不要剪切、拼接或混音媒体文件;如果 Editframe 属性可以表达相应编辑,就使用该属性。
- 不要手动安排字幕文本的时间。请向
ef-captions提供按词计时的转录数据。
快速开始
<ef-timegroup mode="sequence" class="w-full h-full">
<ef-timegroup mode="fixed" duration="2s" class="absolute w-full h-full">
<ef-image src="logo.png" class="size-full object-contain"></ef-image>
<ef-text class="absolute inset-x-0 bottom-12 text-center text-white text-3xl">My Brand</ef-text>
</ef-timegroup>
<ef-timegroup class="absolute w-full h-full">
<ef-video src="main.mp4" class="size-full object-cover"></ef-video>
</ef-timegroup>
</ef-timegroup>
import { Timegroup, Video, Image, Text, TimelineRoot } from "@editframe/react";
const MyVideo = ({ id }: { id: string }) => (
<Timegroup id={id} mode="sequence" className="w-full h-full">
<Timegroup mode="fixed" duration="2s" className="absolute w-full h-full">
<Image src="logo.png" className="size-full object-contain" />
<Text className="absolute inset-x-0 bottom-12 text-center text-white text-3xl">My Brand</Text>
</Timegroup>
<Timegroup className="absolute w-full h-full">
<Video src="main.mp4" className="size-full object-cover" />
</Timegroup>
</Timegroup>
);
export const App = () => <TimelineRoot id="my-video" component={MyVideo} />;
此示例做出两个时长决策。Logo 卡片使用 fixed duration="2s"。Logo 没有自然的显示时长,因此此时长是编辑上的选择。
第二个场景默认采用 contain 模式。它唯一的子元素是 main.mp4,因此片段决定了场景时长。更长的音频同级元素会延长此场景。关于音乐铺底,请参阅“时间组与序列”下的 fit 示例。
每个进行渲染或导出的 React 合成都需要使用 TimelineRoot 包装器。原因请参阅下文的 React 部分。
此示例直接加载媒体,不使用 <ef-configuration>/<Configuration>。如需本地 JIT 转码、API 路由或签名,请添加此包装器。请参阅下文“高级 → 配置”。
时长单位
5s(秒)| 1000ms(毫秒)| 2m(分钟)
使用 npm create @editframe 开始新项目。详情请参阅 editframe-create 技能。
核心概念
时间模型
每个元素——ef-timegroup、ef-video、ef-text 等——都是一个时间元素。每个时间元素都包含两个以秒为单位的时间属性:
duration— 元素的时长。ownCurrentTime— 元素本地播放头的位置。Editframe 根据最近的祖先元素的时间以及元素自身的offset/窗口,推导出此值。
媒体元素还包含一个时间属性:currentSourceTime。这是经过 sourcein/修剪后,在底层源文件中的位置。
只有根元素的 currentTime 支持直接写入。使用 seek() 或 seekForRender() 设置它。Editframe 会从根元素推导出所有其他时间值。每个推导出的值都会沿树单向向下传递。
这些属性的 *Ms 变体(durationMs、ownCurrentTimeMs 等)仍然存在,以保持向后兼容。这些变体已弃用。请在新代码中使用以秒为单位的字段。
Editframe 支持四个修剪属性:
sourcein/sourceout— 选择源文件中的一个窗口。trimstart/trimend— 从元素自身时长的开头或结尾剪去时间。
使用 offset 在 fixed、contain 或 fit 父元素中定位子元素。
时间组与序列
ef-timegroup 是唯一的容器元素。它的 mode 属性控制两件事:时间组自身的时长,以及每个子元素的时间窗口。下表列出了各个模式。
| Mode | Duration | Children |
|---|---|---|
fixed | duration 属性。 | 每个子元素按自己的 offset 定位,并行播放。 |
sequence | 子元素时长之和,减去每对相邻子元素之间的 overlap。 | 连续播放;仅显示当前活动的子元素(通过 hidden 切换,并设置 data-active 属性)。 |
contain(默认) | 所有子元素的 offset + duration 中最晚的时间点。 | 并行播放,每个子元素按自己的 offset 定位。 |
fit | 继承最近的祖先时间组的时长(如果它是根元素,则回退到 contain)。 | 并行播放,每个子元素按自己的 offset 定位。 |
使用 sequence 模式连续播放一系列场景。请参阅上面的快速入门示例。设置 overlap(CSS 时间值,例如 overlap="1s"),即可让下一个场景提前相应时长开始。这样,下一个场景就能与上一个场景的尾段同时播放,形成交叉淡化或转场窗口。
选择模式时,只需问一个问题:是媒体决定此元素的时长,还是由你决定?
- 由媒体决定。 用于视频片段、旁白或其他时长取决于自身内容的元素。不要设置
duration。将mode留空,使其默认采用contain,此时时间组的时长由其内容决定。这样,合成内容就变成了可复用的模板。你可以先构建时间线,再确定最终媒体的时长。之后替换为时长不同的片段,时间安排仍然有效,无需手动修改。 - 由编辑决定。 用于标题卡片、固定时长的片尾或其他由你控制时长的节拍。这类内容没有可推导的自然时长。设置
fixed duration="..."。 - 继承。
fit模式始终跟随最近的祖先时间组的时长,而非自身内容的时长。当某个元素必须与一个时长尚未确定的相对元素完全等长时,使用fit。最常见的情况是:在由场景组成的sequence下方放置背景音轨。
<ef-timegroup class="w-full h-full">
<ef-timegroup mode="sequence" class="absolute w-full h-full">
<ef-timegroup mode="fixed" duration="3s" class="absolute w-full h-full">
<ef-video src="a.mp4" class="size-full object-cover"></ef-video>
</ef-timegroup>
<ef-timegroup class="absolute w-full h-full">
<ef-video src="b.mp4" class="size-full object-cover"></ef-video>
</ef-timegroup>
</ef-timegroup>
<ef-timegroup mode="fit">
<ef-audio src="bed.mp3" volume="0.3"></ef-audio>
</ef-timegroup>
</ef-timegroup>
背景音轨的播放时长与序列完全一致。今天,序列时长为 8 秒。明天更换片段后,序列时长变为 12 秒。无论哪种情况,都无需手动同步时长。
不要默认将每个场景都设为 fixed。这样会失去模板的主要优势。每次媒体源时长变化时,你都得手动重新调整时间,而无法让合成内容自动适配。
转场
利用 sequence 时间组的 overlap 构建转场。Editframe 没有单独的转场元素。在序列上设置 overlap 后,Editframe 会为每个发生重叠的子场景设置两个 CSS 自定义属性:
--ef-transition-duration— 重叠时长,以秒为单位。--ef-transition-out-start— 场景应开始播放退出动画的时间点,以秒为单位。该值等于duration - overlap。
根据这两个属性设置场景淡化、滑动或缩放动画的延迟和时长。Editframe 会将动画定位到场景的时钟,因此无论如何定位或渲染,退出动画都能保持正确。
<ef-timegroup mode="sequence" overlap="0.5s">
<ef-timegroup mode="fixed" duration="3s" class="scene">
<ef-video src="a.mp4" class="size-full object-cover"></ef-video>
</ef-timegroup>
<ef-timegroup mode="fixed" duration="3s" class="scene">
<ef-video src="b.mp4" class="size-full object-cover"></ef-video>
</ef-timegroup>
</ef-timegroup>
<style>
.scene {
animation: fade-out linear;
animation-duration: var(--ef-transition-duration, 0s);
animation-delay: var(--ef-transition-out-start, 9999s);
animation-fill-mode: forwards;
}
@keyframes fade-out {
to { opacity: 0; }
}
</style>
时间变量
每次定位时,Editframe 都会在每个时间元素上发布两个 CSS 自定义属性:
--ef-duration— 元素自身的时长,采用 CSS 时间值(例如5s)。--ef-progress— 取值范围为 0 到 1 的数字,无单位。
使用这些属性来设置随元素时钟变化的样式。无需脚本。
ef-timegroup[mode="fixed"] .progress-bar {
transform: scaleX(var(--ef-progress));
}
ef-text 片段(见下文)还会获得 --ef-index、--ef-word-index、--ef-stagger-offset 和 --ef-seed。ef-captions 的单词会获得 --ef-word-index、--ef-word-seed 和 --ef-word-start。
脚本编写
Editframe 提供两个钩子,让你运行与合成时钟同步的 JavaScript。请使用这些钩子,而不要使用 requestAnimationFrame。
切勿使用 requestAnimationFrame、setInterval 或墙上时钟计时器来驱动逐帧状态。 浏览器会在标签页或窗口转入后台时降低 requestAnimationFrame 回调的频率,甚至完全暂停回调。无头和离屏导出始终在这种后台状态下运行。每当预览标签页失去焦点时,普通编辑也经常处于这种状态。由 requestAnimationFrame 驱动的合成会悄无声息地停止动画,或与渲染时钟失去同步。
addFrameTask 不存在这些问题。定位/渲染管线会在每个渲染帧直接调用它。无论标签页是否可见,它都能保持正确。
timegroup.initializer = (timegroup) => { ... }— 这是一个仅限 JavaScript 的属性,不是 HTML 属性。它对每个实例只运行一次,对每个渲染克隆也只运行一次(见下文“渲染”)。请将其编写为同步函数。不要在其中等待任何操作。可用它来注册addFrameTask回调并设置状态。你可以在其中调用resolveDuration()或addFrameTask。如果抛出错误,或运行时间超过 10ms,则会记录警告;运行时间超过 100ms 则会记录错误。timegroup.addFrameTask((info) => { ... })— 注册逐帧回调。Editframe 会在每次定位时调用此回调,并传入{ ownCurrentTime, currentTime, duration, percentComplete, element }(所有时间单位均为秒)。此方法会返回一个注销函数。
<ef-timegroup id="scene" mode="fixed" duration="5s">
<ef-text id="counter" class="absolute inset-0 flex items-center justify-center text-6xl"></ef-text>
</ef-timegroup>
<script>
const scene = document.getElementById("scene");
scene.initializer = (timegroup) => {
const counter = timegroup.querySelector("#counter");
timegroup.addFrameTask(({ ownCurrentTime }) => {
counter.textContent = Math.floor(ownCurrentTime * 10).toString();
});
};
</script>
在 React 中,将相同的函数作为 <Timegroup> 的 initializer/onFrame 属性传入。onFrame 是对单次 addFrameTask 调用的简写。
媒体元素
所有视频、音频和图像源都通过 ef-video、ef-audio 或 ef-image 加载。每个元素都是时序叶元素。Editframe 会在预览和导出时,将其定位到合成时间。导出时会混合 ef-video 和 ef-audio 元素的声音。
通过属性而非单独的媒体工具来编辑媒体:
- 使用
sourcein和sourceout选择源素材的部分内容。 - 使用
trimstart和trimend从元素自身的时长中裁剪时间。 - 使用
offset将片段或声音提示放置到场景中。 - 使用
volume设置音量,或使用mute将其静音。 - 使用
ef-surface再次显示视频解码后的帧。
完整属性列表请参阅下方的元素参考:裁剪、音量、变体阶梯、FFT 分析等。少数元素的行为值得进一步说明。
ef-text 会将其文本内容拆分为 ef-text-segment 子元素。设置 split="word"、split="char" 或 split="line",以选择拆分单位。设置 stagger(CSS 时间值)可延迟每个片段的起始时间。
每个 ef-text-segment 都在轻量 DOM 中渲染,不带影子根。它带有 --ef-index、--ef-word-index、--ef-stagger-offset 和 --ef-seed 自定义属性。
将入场动画附加到片段本身。每个片段在开始后都会保持可见。下一个交错片段开始时,该片段的 data-active 属性会结束。可使用此属性实现临时强调。如果下一个片段在动画结束前开始,[data-active] 上的入场动画就会提前停止。
@keyframes pop-in {
from { opacity: 0; transform: scale(0.5); }
}
ef-text-segment {
animation: pop-in 0.4s calc(var(--ef-stagger-offset)) both;
}
ef-captions 支持通过以下三种方式接收字幕数据:
captions-src— JSON 文件的 URL。captions-script— 内嵌<script type="application/json">元素的 ID。captions.captionsData = {...}— JavaScript 属性,而非 HTML 属性。用于代码动态生成的文字稿。
JSON 数据采用 segments 或 word_segments 结构。每个单词或片段都包含 start 和 end,单位可以是秒或毫秒。Editframe 会根据数值大小判断单位。
通过 ef-captions-active-word 部件或子元素设置当前单词的样式。也可以使用 --ef-word-index/--ef-word-seed 实现逐词交错效果。
ef-waveform 可将 ef-audio 或 ef-video 的 target 可视化。选择 mode:bars、line、curve、bricks、pixel、wave、spikes 或 roundBars。
ef-surface 会将另一个元素的像素镜像到自身画布上。将 target 设置为画布或任意 HTMLElement。可用它在第二个样式不同的元素中重用视频解码后的帧,而无需重新解码。
渲染
浏览器导出。 调用 @editframe/elements 中的 renderTimegroupToVideo(timegroup, options),将实时布局的 ef-timegroup 编码为视频文件。此函数使用 WebCodecs,并驱动与播放相同的 seekForRender 流程。
关键选项:
width/height/fpsfrom/to— 导出范围,单位为秒。videoCodec/audioCodec/videoBitrate/audioBitratetarget—mediabunny输出目标。signal—AbortSignal。onProgress(({ frame, totalFrames, time, duration }) => ...)
直接调用会跳转并更改传入的 timegroup。仅当该合成不是正在进行实时交互预览的内容时,才直接调用它。
要导出他人正在主动查看或编辑的合成,请勿直接调用 createRenderClone。此函数是内部原语,并非公共 API。请改用以下两种方式之一:
- 对于应用内的自定义导出操作,请调用
ef-workbench的exportVideo()方法(参见editor-gui技能)。此方法会为你克隆、渲染并释放屏幕外副本。 - 对于云端渲染(
@editframe/api中的createRender),无需额外操作。此流程已通过window.EF_RENDER运行;它会自动安装并为你处理屏幕外克隆。
自定义渲染数据。 渲染宿主(例如 Playwright 脚本)可以在运行合成之前,将任意 JSON 注入 window.EF_RENDER_DATA。在浏览器中,使用 getRenderData<T>()(模块级函数)或 useRenderData<T>() React Hook 读取此数据。可用它为渲染设置参数,例如传入用户姓名,而无需对合成本身进行模板化。
运行时和云端渲染。 此技能仅涵盖浏览器内导出。有关通过已安装的 Editframe 运行时进行导出,请参见 editframe-api 技能中的 CLI 部分。有关服务器端渲染和转录任务,请参见 editframe-api 技能的其余部分。
React
每个需要渲染或导出的 React 合成都必须使用 TimelineRoot 包装器。TimelineRoot 会挂载你的合成,也会注册克隆工厂。exportVideo() 和 window.EF_RENDER(参见上文“渲染”)会使用此工厂创建屏幕外渲染克隆。借助此工厂,克隆是一个实时的第二个 React 挂载,Hook、initializer 和 onFrame 闭包都能正常工作。没有它,克隆只是一个缺少这些功能的失效 cloneNode 副本。
const MyTimeline = ({ id }: { id: string }) => (
<Timegroup id={id} mode="sequence">
<MyScenes />
</Timegroup>
);
<TimelineRoot id="root" component={MyTimeline} />
component 属性必须是 React.ComponentType<{ id: string }>。它必须在根节点直接渲染 <Timegroup id={id}>,或通过嵌套的自定义组件进行渲染。每次挂载时(无论实时挂载还是克隆挂载),TimelineRoot 都会将 id 转发给 component。
Hook(均来自 @editframe/react):
useTimingInfo()— 将返回的ref附加到Timegroup。组件会在每一帧使用{ ownCurrentTime, duration, percentComplete }(单位为秒)重新渲染。useMediaInfo(ref)— 将 ref 传给Video或Audio元素。返回{ readyState, duration, currentTime, paused }。usePanZoomTransform(ref)— 将 ref 传给PanZoom元素。返回{ transform: { x, y, scale }, reset(), fitToContent(padding?), screenToCanvas(x, y), canvasToScreen(x, y) }。useRenderData<T>()— 参见上文“渲染”。usePlayback(target)— 订阅播放、暂停、跳转和音量状态,用于构建自定义预览控件。editor-gui技能中的现成 GUI 组件可替代此 Hook 的大多数用法。
高级
配置。 ef-configuration/<Configuration> 提供媒体路由和身份验证设置。当本地媒体可直接加载且不需要 API 路由或签名时,此项为可选。
在使用已安装 Editframe 运行时的项目中,媒体由运行时管理。在自定义设置中进行本地 JIT 转码时,请在媒体子元素连接之前,在其祖先配置上设置 api-host。使用提供本地媒体路由的浏览器源。有关这两种设置,请参阅 dev-server 技能。
对于经过身份验证的媒体,请为部署配置 api-host 和 signing-url。image-proxy 属性控制跨源图像代理。完整属性列表请参阅下方的元素参考。
media-engine 保留供将来使用。目前唯一的引擎是本地渲染。
React Three Fiber。 使用 @editframe/react/r3f 中的 <CompositionCanvas> 包裹 <Timegroup> 子元素。这会提供一个与合成时间同步的 R3F <Canvas>。在场景组件中使用 useCompositionTime() 读取时钟,该函数以秒为单位返回 { time, duration }。
<Timegroup mode="fixed" duration="14s">
<CompositionCanvas shadows>
<MyScene />
</CompositionCanvas>
</Timegroup>
要在 Web Worker 中渲染 3D 并释放主线程,请使用 OffscreenCompositionCanvas。自行构造 Worker。在 worker 脚本中调用 @react-three/offscreen 自带的 render() 函数来驱动它。没有 renderOffscreen 导出项。
服务器端渲染。 @editframe/react/server 和 @editframe/elements/server 仅导出合成组件和类型。它们不包含自定义元素注册,也不包含 DOM、canvas 或 WebCodecs 代码。可在 Next.js 或 Remix 服务器代码中安全导入。
这些服务器入口点不包含 GUI 组件或钩子。请改为在客户端渲染这些内容,例如使用 dynamic(() => import("@editframe/react"), { ssr: false }) 或等效方式。
对于 Next.js,请使用 @editframe/nextjs-plugin 中的 withEditframe() 包裹 next.config.mjs,并添加其路由处理程序。有关设置和选项,请参阅 dev-server 技能。有关可运行示例,请参阅 editframe-create 技能的 nextjs 模板。
任一服务器入口点中的 getRenderInfo() 在调用时仍需要一个挂载了 ef-timegroup 的活动 document。仅在已渲染的页面内调用它,例如在 Playwright 脚本或云渲染主机中。不要在服务器上用它解析 HTML 字符串。
包入口点
| 导入 | 环境 | 包含内容 |
|---|---|---|
@editframe/elements | 浏览器 | 所有自定义元素、canvas/WebCodecs 渲染。 |
@editframe/elements/server | 浏览器、Node、SSR | 仅类型,以及 getRenderInfo()(运行时仅限浏览器)。 |
@editframe/elements/gui | 浏览器 | 编辑器 GUI 自定义元素(时间线、播放头、控制柄等)——请参阅 editor-gui 技能。 |
@editframe/elements/styles.css, /theme.css | 浏览器 | 基础样式和主题样式。 |
@editframe/react | 浏览器 | React 合成和 GUI 组件、钩子。 |
@editframe/react/server | 浏览器、Node、SSR | 仅合成组件,不含钩子或 GUI。 |
@editframe/react/r3f | 浏览器 | CompositionCanvas、OffscreenCompositionCanvas、useCompositionTime。 |
样式
使用 CSS 或 Tailwind 为元素设置样式。样式控制布局和外观。Editframe 元素和属性控制时间。使用 absolute 定位子元素。使用 w-full h-full 或 size-full 设置其大小。
合成实践
选择一种支持预期顺序和后续编辑的结构。以下指导适用于 HTML 和 React 合成。
场景结构
当场景需要独立的本地时钟时,使用嵌套的时间组。根据上述时长模型选择 contain、fixed 或 fit。场景的 ownCurrentTime 从零开始,因此其动画不受之前场景影响。
sequence 会对所有相邻子项应用同一个 overlap 值。如果过渡需要不同的时长,请使用不同的组或显式偏移。当重叠时长小于相邻两个场景的时长时,总时长等于 sum(durations) − overlap × (n−1)。每个声明的场景时长都包含其过渡窗口。
sequence 会隐藏未激活的场景。只有在视觉设计需要时才添加场景级不透明度,例如交叉淡化。过渡变量描述的是重叠窗口,并不要求所有元素同时进入或退出。
将场景专属动画放在场景内部。当运动涉及多个场景或持久覆盖层时,使用父级帧任务。根据提供的时间推导状态,以确保直接跳转和反向跳转都能重现相同画面。增量式模拟需要确定性的重置和重播策略。
动画
先通过 Editframe 结构设置时序。为每个节拍设置独立的时间组。使用 offset 将元素放置在节拍内。使用 ef-text 的 split 和 stagger 显示文本。使用过渡变量退出场景。
然后选择每个视觉属性的变化方式:
- 对于可用固定时间线描述的运动,使用关键帧。Editframe 会根据时间元素的时钟调整关键帧。
- 对于程序化几何图形、计数器或其他计算状态,使用
addFrameTask或 ReactonFrame。 - 对于随元素进度变化的样式,使用
--ef-progress。
根据设计安排入场、停留和退出,而不是套用单一的显示模式。选择显示效果、过渡或注意力引导方式时,使用运动设计目录。
- 对于重复元素,根据索引一次性计算固定延迟。
- 对于环境循环,使用重复关键帧,并为每个项目设置独立相位。
- 对于独立变换,使用嵌套包装器或显式变换组合。
- 对于在序列重叠期间退出的元素,使用
--ef-transition-out-start和--ef-transition-duration。
根据动画前后的预期状态设置动画填充模式:
- 使用
backwards,在延迟期间应用第一个关键帧。 - 使用
forwards,在动画完成后保留最后一个关键帧。 - 当两种状态都需要保留时,使用
both。
这些规则同样适用于文本片段。Editframe 不会自动添加填充模式。检查同一属性上是否存在重叠动画;保留的状态可能会覆盖其他动画。
CSS 变换动画默认会替换静态变换。单独的包装器可以保留布局变换(例如居中),同时让内部元素执行动画。
资源
使用预览环境和导出环境都能解析的资源 URL。使用已安装的 Editframe 运行时,通过浏览器编辑器或 Agent 运行时导入媒体。将每个新源文件添加到插件的 source.files 列表中。对于自定义设置中的本地媒体路由,本地路径会相对于配置的 root 解析。
当打包器必须包含资源时,使用资源导入或其他受支持的打包器引用。仅将文件放在 src/ 中不会内联文件。公共目录中的文件需要托管环境或单独的打包步骤。检查最终导出环境,确保没有无法解析的 URL。
如果后续更改需要可编辑的源资源,请保留它们。对于修剪和偏移等时间编辑,使用合成属性。如果所需效果有要求(例如响度标准化),请预处理媒体。避免反复对唯一的源文件进行破坏性编辑。
在 React 中,<Image> 会包装一个自定义元素。它不接受 alt 属性。请使用与 HTMLElement 兼容的元素引用,而不是 HTMLImageElement。
音频
当预览时间与导出时间需要保持一致时,请在合成中加入音频。
sequence 的直接子元素会成为另一个节拍。在外层时间组下,将连续的音乐背景与序列并列放置。对于时长未知的背景音乐,请使用 fit,如上所示。对于固定时长,请将背景音乐限制为该时长。若短音频源需要覆盖更长的视频,则必须明确指定重复策略。
将特定场景的音效提示放在该场景内。将 offset 设为相对于场景起始位置的偏移。使用 sourcein/sourceout 和 trimstart/trimend 选择源内容并进行时间裁剪。
volume 是线性增益值,而非分贝值。这些元素会将其限制在 0–1 的范围内。大于 1 的值不会放大源音频。需要放大时,请离线应用增益。请检查是否存在削波。
验证
开发期间请检查有代表性的帧,包括开场、文字密集的帧、转场边界和结尾。在等待 seekForRender() 完成后截取浏览器画面,可用于检查布局。导出的帧也能揭示捕获差异。
交付前请渲染预期输出。观看渲染后的视频,检查时序、连贯性、可读性和转场。聆听音效提示的位置、语音清晰度、音乐平衡和削波情况。仅仅成功编码并不能证明视觉或音频质量合格。
检查输出元数据中的时长、尺寸、帧率和音频流。请使用实际输出路径:
ffprobe output/demo.mp4
ffmpeg -i output/demo.mp4 -af volumedetect -f null -
音量统计有助于发现静音或削波风险,但不能替代聆听。如果无法渲染或检查,请说明限制以及仍需进行的检查。
发送反馈
如有问题或改进建议,请向 Editframe 发送反馈:
- 如果 Editframe 或此技能导致问题,请找到变通方法并继续任务。然后报告问题和变通方法。
- 如果你发现有助于其他任务(而不仅是当前任务)的改进,请发送建议。例如,建议改进说明、补充示例、优化默认设置或新增功能。
每个不同的问题或建议分别发送一份报告。如果报告发送失败,请继续任务。
curl -sS https://editframe.com/api/v1/feedback \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $EDITFRAME_API_KEY" \
--data-binary @- <<'JSON'
{
"category": "bug",
"skill": "composition",
"summary": "One line that names the problem or the suggestion",
"details": "For a problem: steps, expected result, actual result, and errors. For a suggestion: the change and how it helps.",
"workaround": "The workaround that you used. Omit this field for a suggestion."
}
JSON
将 category 设为 bug、documentation、feature_request、performance 或 other。API 密钥为可选项。不提供密钥的报告将以匿名方式提交。请勿在报告中填写机密信息、API 密钥或个人数据。所有字段、限制和响应请参阅 editframe-api 技能。
元素参考
ef-audio
<ef-audio> 在合成中播放音频。
属性
| 属性 | React 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|---|
duration | duration | string | — | 此元素保持活动状态的时长(CSS 时间,例如 "5s")。 |
fft-decay | fftDecay | number | 8 | 在 getFrequencyData 中进行平均的衰减帧数(默认值为 8)。 |
fft-gain | fftGain | number | 3 | 频率分析前应用的线性增益(默认值为 3.0)。 |
fft-size | fftSize | number | 128 | getFrequencyData/getTimeDomainData 的 FFT 窗口大小(会舍入为 2 的幂,默认值为 128)。 |
file-id | fileId | string | null | — | 云文件标识符——解析为 ${apiHost}/api/v1/files/${fileId},优先级高于 src。 |
interpolate-frequencies | interpolateFrequencies | boolean | false | 通过在 FFT 频段之间插值,而非使用最近邻采样,平滑 getFrequencyData 频段数据。 |
loop | loop | boolean | false | 循环属性——当此时间元素是 PlaybackController 下的自驱动根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
mute | muted | boolean | false | 静音此元素的音频。 |
offset | offset | number | — | 在并行(fixed/contain/fit)父级时间组中的起始偏移(秒)。 |
sourcein | sourcein | string | — | 源文件中的起始时间(CSS 时间,例如 "1.5s")。 |
sourceout | sourceout | string | — | 源文件中的结束时间(CSS 时间,例如 "1.5s")。 |
src | src | string | — | 源音频文件的 URL。 |
trimend | trimend | string | — | 从源文件末尾裁去的时长(CSS 时间)。 |
trimstart | trimstart | string | — | 从源文件开头裁去的时长(CSS 时间)。 |
volume | volume | number | 1 | 音量(0.0 到 1.0)。 |
触发事件
child-duration-changed({ duration: number })——嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-captions
<ef-captions> 显示同步字幕,并逐词高亮。
属性
| 属性 | React 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|---|
captions-script | captionsScript | string | "" | 包含字幕数据的 <script type="application/json"> 元素的 ID,可替代 captionsSrc。 |
captions-src | captionsSrc | string | "" | 包含 segments/word_segments 字幕数据的 JSON 文件的 URL 或路径。 |
duration | duration | string | — | 此元素保持活动状态的时长(CSS 时间,例如 "5s")。 |
loop | loop | boolean | false | 循环属性——当此时间元素是 PlaybackController 下的自驱动根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
offset | offset | number | — | 在并行(fixed/contain/fit)父级时间组中的起始偏移(秒)。 |
target | target | string | "" | 这些字幕所对应的 <ef-video>/<ef-audio> 元素的 ID。 |
word-style | wordStyle | string | "" | 自由格式的样式钩子(无内置行为)——与 @editframe/elements 的 word-style 属性保持一致,确保以此为目标的使用方 CSS/选择器仍能正常工作(例如 ef-captions[word-style="highlight"])。 |
触发事件
child-duration-changed({ duration: number })——嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-captions-active-word
<ef-captions-active-word> 包裹 <ef-captions> 当前正在读出的单词。
ef-captions-after-active-word
<ef-captions-after-active-word> 包裹 <ef-captions> 中活动单词之后的字幕文本。
ef-captions-before-active-word
<ef-captions-before-active-word> 包裹 <ef-captions> 中活动单词之前的字幕文本。
ef-captions-segment
<ef-captions-segment> 将一行字幕的前置、当前和后续单词元素分组。除非你在 <ef-captions> 内自行编写此元素,否则 <ef-captions> 会自动创建它。
ef-configuration
<ef-configuration> 是根配置元素。
属性
| 属性 | React 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|---|
api-host | apiHost | string | "" | 用于文件/资源请求的 Editframe API 基础 URL。 |
image-proxy | imageProxy | "auto" \ | "none" | "auto" |
media-engine | mediaEngine | string | "local" | 为未来的引擎切换预留;本地渲染始终使用默认编解码器集。 |
signing-url | signingURL | string | "/@ef-sign-url" | 用于请求已签名持有者令牌,以便通过跨源媒体访问进行身份验证的 URL(默认值为 "/@ef-sign-url",与 @editframe/elements 匹配)。 |
触发事件
playback-attached({ controller: PlaybackController })— 当EFConfigurationElement.attachPlayback创建新的PlaybackController时触发。这样一来,在附加操作发生前已解析其PlaybackBridge的 GUI 控件无需轮询即可获知该操作。
ef-image
<ef-image> 是静态图像时间叶节点。
属性
| 属性 | React 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|---|
asset-id | assetId | string | null | — | 已弃用的 file-id 别名。 |
duration | duration | string | — | 此元素的活动时长(CSS 时间,例如 "5s")。 |
file-id | fileId | string | null | — | 云端文件标识符 — 解析为 ${apiHost}/api/v1/files/${fileId},优先级高于 src。 |
loop | loop | boolean | false | 循环属性 — 当此时间元素是 PlaybackController 下自行驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
offset | offset | number | — | 在并行(fixed/contain/fit)父级时间组中的起始偏移(秒)。 |
src | src | string | — | 源图像文件的 URL。 |
CSS 部件
canvas— 渲染画布。此画布是一个轻 DOM 子元素,而非影子 DOM 元素。
触发事件
child-duration-changed({ duration: number })— 当嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。contentchange({ reason: string })— 当图像源发生影响渲染的变化时触发。例如,src、file-id或asset-id的新值都会触发此事件。
ef-motionblur
<ef-motionblur> 为其子元素应用运动模糊。
属性
| 属性 | React 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|---|
amount | amount | number | 0 | 以像素为单位的显式模糊量。 |
angle | angle | number | 0 | 以度为单位的显式模糊方向。 |
fps | fps | number | 30 | 用于将 shutterAngle 转换为快门时长的帧率。 |
sensitivity | sensitivity | number | 1 | 计算模糊量之前应用于检测到的运动的乘数。 |
shutter-angle | shutterAngle | number | 180 | 以度为单位的模拟相机快门角 — 自动采样时控制模糊强度(默认值为 180)。 |
threshold | threshold | number | 0.5 | 应用任何模糊之前的最小运动幅度(像素/度)。 |
ef-pan-zoom
<ef-pan-zoom> 是一个平移和缩放视口。
属性
| 属性 | React 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
auto-fit | autoFit | boolean | false | 自动将内容适配到视口,而不是使用显式的 x/y/scale。 |
scale | scale | number | 1 | 当前缩放比例。 |
x | x | number | 0 | 当前水平平移偏移量,单位为像素。 |
y | y | number | 0 | 当前垂直平移偏移量,单位为像素。 |
CSS 部件
content— 经过变换的包装元素。它会对插槽中的内容应用平移和缩放。
触发事件
transform-changed({ x: number; y: number; scale: number; })— 每次平移或缩放时,都会携带{ x, y, scale }触发。
方法
screenToCanvas(screenX: number, screenY: number): { x: number; y: number }— 将屏幕坐标(例如event.clientX和event.clientY)转换为画布坐标。canvasToScreen(canvasX: number, canvasY: number): { x: number; y: number }— 将画布坐标转换为屏幕坐标。reset(): void— 将平移和缩放重置为恒等变换(x: 0, y: 0, scale: 1)。fitToContent(padding?: number): void— 将内容居中并缩放,使其适配视口。padding参数用于设置边距,取值为 0 到 1 之间的数字。
ef-surface
<ef-surface> 会将另一个元素的像素镜像到内部画布上。
属性
| 属性 | React 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
duration | duration | string | — | 此元素处于活动状态的时长(CSS 时间,例如 "5s")。 |
loop | loop | boolean | false | 循环属性 — 当此时间元素是 PlaybackController 下由自身驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
offset | offset | number | — | 在并行(fixed/contain/fit)父级时间组中的起始偏移量(秒)。 |
target | target | string | "" | 要镜像的元素 ID(位图源或任意 HTMLElement)。 |
CSS 部件
canvas— 用于渲染的画布。此画布是一个轻 DOM 子元素,而非影子 DOM 元素。
触发事件
child-duration-changed({ duration: number })— 嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-text
<ef-text> 是带有行、单词或字符拆分效果的动画文本。
属性
| 属性 | React 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|---|
blur-amount | blur-amount | number | — | 设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 amount。 |
blur-angle | blur-angle | number | — | 设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 angle。 |
blur-fps | blur-fps | number | — | 设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 fps。 |
blur-sensitivity | blur-sensitivity | number | — | 设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 sensitivity。 |
blur-shutter-angle | blur-shutter-angle | number | — | 设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 shutter-angle。 |
blur-threshold | blur-threshold | number | — | 设置 motion-blur 时,转发给包装它的 <ef-motionblur> 的 threshold。 |
duration | duration | string | — | 此元素处于活动状态的时长(CSS 时间,例如 "5s")。 |
easing | easing | string | "linear" | 应用于错开时间的 CSS 缓动关键字。 |
loop | loop | boolean | false | 循环属性——当此时间元素是 PlaybackController 下由自身驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
motion-blur | motionBlur | boolean | false | 将每个片段包装在 <ef-motionblur> 元素中,以自动检测方向模糊。 |
offset | offset | number | — | 在并行(fixed/contain/fit)父级时间组中的开始偏移量(秒)。 |
split | split | "line" \ | "word" \ | "char" |
stagger | stagger | string | "" | 每个拆分片段开始动画之间的延迟(CSS 时间,例如 "100ms")。 |
触发事件
child-duration-changed({ duration: number })——当嵌套媒体元素的时长确定或发生变化时,在最近的提供上下文的祖先元素上触发。
ef-text-segment
<ef-text-segment> 是 EFTextElement 的一个错开动画单元。
ef-timegroup
<ef-timegroup> 是一个时间容器。
属性
| 属性 | React prop | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
duration | duration | string | — | 当 mode="fixed" 时的固定时长(CSS 时间值,例如 "5s");在其他模式下会被忽略。 |
fps | fps | number | — | 渲染帧率。 |
loop | loop | boolean | false | 循环属性——当此时间元素是在 PlaybackController 下自行驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
mode | mode | "fixed" \ | "sequence" \ | "contain" \ |
offset | offset | number | — | 在并行(fixed/contain/fit)父级 timegroup 中的开始偏移(秒)。 |
overlap | overlap | number | 0 | sequence 模式下相邻项目之间的重叠时间(例如 "1s")。 |
触发事件
child-duration-changed({ duration: number }) — 嵌套媒体元素的时长已确定或发生变化时,在最近的提供上下文的祖先元素上触发。playback-attached({ controller: PlaybackController }) — TODO:记录此事件。
方法
addFrameTask(callback: FrameTaskCallback): () => void— 注册逐帧回调函数。此方法会返回一个注销函数。waitForContentReady(): Promise<void>— 等待此 timegroup 以及与当前呈现相关的时间子元素就绪。在 sequence 模式下,此方法只等待当前活动场景。曾经对每个空闲的同级元素都进行等待,导致 Showcase 导出无限期挂起,因为后续片段的#load()方法与共享注册表输入发生争用。seekForRender(time: number, options?: SeekForRenderOptions): Promise<void>— 当strictVideoPaint为true时,此方法还会验证每个活动的ef-video后代是否都绘制了覆盖此次 seek 播放头位置的样本,而不只是确认画布上仍有较早的样本(hasPaintedFrame是粘滞状态)。画布上的陈旧帧会导致剪辑点处编码出 N-1。方法会在抛出错误前短暂重试。此检查会额外消耗几个微任务周期,因此请将其用于最终渲染捕获,而非交互式预览。有关此方法所基于的确定性同步和内容就绪约定,请参阅基类TemporalElement.seekForRender方法。options.strictVideoPaint(可选):boolean— 为 true 时,还会验证每个活动的<ef-video>后代是否为此帧绘制了样本。仅元数据状态已就绪还不够。完整检查说明请参阅EFTimegroupElement.seekForRender。此选项在基类实现中无效,因为只有容器才能遍历其后代。options.paintPhaseTiming(可选):boolean— 发出严格绘制阶段诊断;验证仍会独立启用。options.preferProxy(可选):boolean— 为 true 时,如果代理层级中有轻量级拖动代理或低质量代理,则优先使用。请在时间线胶片条和合成缩略图中设置此选项。这样可避免繁重场景仅为绘制小型预览,就打开每个片段的播放层级(role=high)输入。最终导出或EF_FRAMEGEN请勿设置此选项,以便sync仍等待播放层级就绪。options.signal(可选):AbortSignal— 使用此信号中止正在进行的准备或呈现步骤,例如 SampleChase 泵或迭代器的next调用。当更新的beginFrame调用替代了一个停滞的调用时,EF_FRAMEGEN会使用此选项。
ef-video
<ef-video> 是视频时间叶节点。
属性
| 属性 | React prop | 类型 | 默认值 | 描述 |
|---|---|---|---|---|
duration | duration | string | — | 此元素处于活动状态的时长(CSS 时间,例如 "5s")。 |
fft-decay | fftDecay | number | 8 | 对 getFrequencyData 进行平均的衰减帧数(默认值为 8)。 |
fft-gain | fftGain | number | 3 | 频率分析前应用的线性增益(默认值为 3.0)。 |
fft-size | fftSize | number | 128 | getFrequencyData/getTimeDomainData 的 FFT 窗口大小(四舍五入为 2 的幂,默认值为 128)。 |
file-id | fileId | string | null | — | 云文件标识符 — 解析为 ${apiHost}/api/v1/files/${fileId},优先级高于 src/variants。 |
interpolate-frequencies | interpolateFrequencies | boolean | false | 通过在 FFT 频带间插值而非采用最近邻采样,平滑 getFrequencyData 频段。 |
loop | loop | boolean | false | 循环属性 — 当此时间元素在 PlaybackController 下作为自动驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
mute | muted | boolean | false | 静音视频的音轨。 |
offset | offset | number | — | 在并行(fixed/contain/fit)父时间组中的起始偏移量(秒)。 |
play-role | playRole | "low" \ | "high" | "high" |
sourcein | sourcein | string | — | 源文件中的起始时间(CSS 时间,例如 "1.5s")。 |
sourceout | sourceout | string | — | 源文件中的结束时间(CSS 时间,例如 "1.5s")。 |
src | src | string | — | 源视频文件的 URL。 |
trimend | trimend | string | — | 从源文件末尾裁去的时长(CSS 时间)。 |
trimstart | trimstart | string | — | 从源文件开头裁去的时长(CSS 时间)。 |
variant-mode | variantMode | "single-high" \ | "scrub-upgrade" \ | "multi-bitrate" \ |
variants | variants | string | "" | 变体阶梯清单的 URL(包含多个分辨率/比特率),用于替代 src。 |
volume | volume | number | 1 | 音量(0.0 到 1.0)。 |
CSS 部件
canvas— 用于渲染帧的画布。此画布是轻量 DOM 子元素,而非 Shadow DOM 元素。
触发事件
child-duration-changed({ duration: number })— 嵌套媒体元素的时长已知或发生变化时,在最近的上下文提供祖先上触发。variantchange({ role: "scrub" \| "low" \| "high"; url: string; window: number \| null })— 活动解码变体发生变化时触发,例如其角色、URL 或拖动预览窗口发生变化。
ef-waveform
<ef-waveform> 是一种实时音频响应型波形可视化元素,与 ef-audio 或 ef-video 目标绑定。
属性
| 属性 | React 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
bar-spacing | barSpacing | number | 0.5 | 基于条形的可视化模式中,条形间距的比例(0-1)。 |
color | color | string | "currentColor" | "currentColor"(默认值)会解析为宿主计算后的 CSS color。 |
duration | duration | string | — | 此元素的活动时长(CSS 时间,例如 "5s")。 |
line-width | lineWidth | number | 4 | 线条/曲线可视化模式中的描边宽度(px)。 |
loop | loop | boolean | false | 循环属性——当此时间元素是 PlaybackController 下由自身驱动的根元素时,控制器会回到 0,而不是在 duration 处暂停。 |
mode | mode | "line" \ | "bars" \ | "roundBars" \ |
offset | offset | number | — | 在并行(fixed/contain/fit)父级时间组中的起始偏移量(秒)。 |
target | target | string | "" | 要进行可视化的 <ef-audio>/<ef-video> 元素的 ID。 |
CSS 部件
canvas— 渲染画布。此画布是轻量 DOM 子元素,而非影子 DOM 元素。
触发事件
child-duration-changed({ duration: number })— 当嵌套媒体元素的时长变为已知或发生变化时,在最近的提供上下文的祖先元素上触发。