资讯动态

HyperFrames v0.6.91:GSAP 感知的时间线分片引擎与 Razor 工具源码解析

发布时间:2026/9/10 15:09:07 来源:尧图企业网站定制
HyperFrames v0.6.91GSAP 感知的时间线分片引擎与 Razor 工具源码解析【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframesHyperFrames v0.6.912026-06-11 发布带来了时间线剪辑工作流的一次实质升级Studio 中新增 Razor剃刀工具用于在任意时刻把时间线上的片段一分为二配套的 Core 分片引擎能理解 GSAP 动画并保留其关键帧语义同时 GSAP drag intercept 被放到默认关闭的 feature flag 之后以修复动画元素上的拖拽异常。读完本文你将掌握 Razor 工具的使用方式、分片引擎在 HTML/GSAP 层面的工作原理、服务端原子切分事务的冲突与回滚机制以及该版本每项变更在仓库源码中的对应实现。版本变更概览v0.6.91 的完整变更见 releases/v0.6.91.md分为三类FeaturesStudioRazor/blade 工具的 UI用于时间线片段分割PR #1331FeaturesCoreGSAP-aware 的时间线分片引擎PR #1330FixesStudioGSAP drag intercept 增加 feature flag 且默认禁用修复动画元素上行为异常的拖拽PR #1341InternalStudio抽取共享时间线组件、消除重复代码PR #1329这是 Razor 工具能干净落地的重构前置。Razor 工具在 Studio 中分割时间线片段Razor 工具的使用方式在官方文档 时间线指南 中有明确说明切换到 razor 工具后单击某个片段即可在播放头所在位置将其切成两段按住 Shift 并使用 razor则在该时刻分割播放头下方的所有合格片段split all快捷键B用于切换 razor 工具见 快捷键文档切完后记得切回选择工具。文档中同时给出了两条使用约束分割点必须位于片段内部不能恰好落在片段的起点或终点某些元素类型无法安全分割例如锁定片段、无稳定标识的元素等。这些约束在源码中有精确的量化实现。分片资格判断 定义了最小边界距离/** Minimum distance (seconds) from clip boundaries to allow a split. */ export const SPLIT_BOUNDARY_EPSILON_S 0.03; export function isSplitTimeWithinBounds( splitTime: number, clipStart: number, clipDuration: number, ): boolean { return ( splitTime clipStart SPLIT_BOUNDARY_EPSILON_S splitTime clipStart clipDuration - SPLIT_BOUNDARY_EPSILON_S ); } export function canSplitElement(el: TimelineElement): boolean { const hasStableIdentity Boolean(el.hfId || el.domId || el.selector); const hasValidRate el.playbackRate null || (Number.isFinite(el.playbackRate) el.playbackRate 0); return ( !el.timelineLocked el.timingSource ! implicit hasStableIdentity hasValidRate !!el.duration Number.isFinite(el.duration) ); }也就是说一个片段要可分割必须同时满足未被锁定!timelineLocked、时序是作者显式声明的timingSource ! implicit、具有稳定标识hfId/domId/selector三者之一、播放速率合法、时长为有限正数并且分割点距片段两端至少 0.03 秒。注释还解释了 0.03s 取值与 UI 的联动时间线画布会把贴边的点击 clamp 到 start/end ± epsilon因此判定必须对 epsilon 偏移采用闭区间/clamp 后的值才能通过校验。canSplitElementAt将两条规则合并且 single-clip 与 split-all 两条 razor 路径共用同一判定避免了此前 split-all 用裸/导致切点落入 epsilon 区间产生退化切片的缺陷。前端交互入口useRazorSplit Hookrazor 点击事件经由 TimelineRazorInteraction 等组件进入 useRazorSplit 这个 React Hook它对外暴露两个处理器handleRazorSplit(element, splitTime)单片段分割成功后弹出Split 片段名 at 时刻s的提示handleRazorSplitAll(splitTime)先通过selectSplittableElements筛选出该时刻所有可分割片段再批量执行。两条路径的共同前置与行为包括录制中禁止编辑isRecordingRef.current为真时直接报错Cannot edit timeline while recording自写窗口执行前将domEditSaveTimestampRef.current Date.now()置为当前时间。源码注释解释了原因——服务端写入会先于 React 状态刷新通过 watcher 到达必须维持一个短暂的自身写入窗口让文件监视器忽略这次由 Studio 自己触发的变更失败反馈事务执行后若syncFailed为真提示切分已保存但预览无法刷新请手动重载预览以重新同步若存在skippedSelectors无法重新定向的选择器则以 info 级 toast 列出具体选择器并说明其动画未被 retarget。Core 分片引擎GSAP-aware 的动画保留把一个 clip 切成两段对纯 HTML 容器只是复制元素、重排start/duration难点在于片段上挂着 GSAP 时间线时动画必须正确归属到前半段或后半段且关键帧语义不能丢失。这正是 v0.6.91 的 Core 侧能力核心实现在 gsapParser 的 Split Animation Functionsexport interface SplitAnimationsOptions { originalId: string; newId: string; splitTime: number; elementStart: number; elementDuration: number; } export interface SplitAnimationsResult { script: string; /** Non-ID-selector animations that the engine cannot safely retarget. */ skippedSelectors: string[]; } export function splitAnimationsInScript( script: string, opts: SplitAnimationsOptions, ): SplitAnimationsResult { const parsed parseGsapScript(script); const originalSelector #${opts.originalId}; const newSelector #${opts.newId}; const skippedSelectors: string[] []; for (const a of parsed.animations) { if (a.targetSelector ! originalSelector a.targetSelector.includes(opts.originalId)) { skippedSelectors.push(a.targetSelector); } } const matching parsed.animations.filter((a) a.targetSelector originalSelector); if (matching.length 0) return { script, skippedSelectors }; let result script; // Reverse iteration: updateAnimationSelector mutates selectors in the source // string, which can shift count-based ID suffixes (e.g. #hero-1 → #hero-2) // for later animations. Processing last-to-first prevents stale ID collisions. for (let i matching.length - 1; i 0; i--) { const anim matching[i]!; const pos typeof anim.position number ? anim.position : 0; const dur anim.duration ?? 0; const animEnd pos dur; ... } }从这段实现可以看出引擎的几项关键设计按 ID 选择器精确匹配动画只有目标选择器恰好等于#originalId的动画才会被重定向到#newId凡是包含 originalId 但不完全相等的选择器例如.hero-1、[data-xhero]之类的非 ID 形式不会被处理而是计入skippedSelectors返回给调用方——这与前端 toast 中非 ID 选择器的动画未被 retarget的提示一一对应行为可解释、可审计逆序处理防 ID 漂移注释明确说明修改选择器会改变源字符串导致基于计数的 ID 后缀如#hero-1→#hero-2发生偏移从后往前处理可避免陈旧 ID 冲突跨越切点的 keyframes 动画宁缺毋滥对带 keyframes 的动画若整段位于切点之后则直接换到新选择器若动画区间跨越切点pos splitTime animEnd源码注释指出跨段 keyframes 无法在不重新归一化百分比与时长的情况下正确拆分于是保留在原片段上并向调用方报告keyframes spanning split。这解释了版本摘要中GSAP-aware keyframe preservation的准确含义——引擎能保留关键帧语义的方式是不做破坏性改写能安全归属的动画严格归属不能安全拆的显式跳过并告警而不是猜一个可能错误的拆分结果纯位置/时长的动画按切点归属对非 keyframes 动画animEnd splitTime的留在前半段pos splitTime的换到后半段新元素跨越切点的同样进入跳过集合保证两段各自的 GSAP 时间线仍然自洽。同文件中还有一处与分片配套的属性分组能力 splitIntoPropertyGroups用于把同一目标上的多属性动画按语义分组是 GSAP 解析层处理复杂时间线的基础设施之一。嵌套合成的坐标重基rebase时间线片段常常位于嵌套合成composition中播放头时刻是展开后的全局时间而片段所在源文件如scene.html中的 GSAP 时间是文件本地时间。buildAtomicCutIntents 中的buildCutTarget负责这个坐标换算以element.expandedParentStart为基准把全局splitTime和elementStart减去父级展开偏移得到目标文件坐标系内的切分点。对应的单测在 razorSplitTransaction.test.ts 中有直接验证it(rebases each nested target into its own source-file coordinates, () { const intents buildAtomicCutIntents( [element({ start: 8, duration: 4, expandedParentStart: 6, sourceFile: scene.html })], 10, index.html, ); expect(intents[0].targets[0]).toMatchObject({ splitTime: 4, elementStart: 2 }); });全局 10s 的切点换算到scene.html父级展开起点 6s、片段自身起点 8s本地坐标即为splitTime: 4, elementStart: 2。同一测试文件还验证了运行时别名去重多个运行时元素若共享同一个domId/hfId同一 authored 宿主的别名只发一次切分意图而不同宿主的 target 会合并进同一文件意图的targets数组由服务端一次原子处理。服务端原子切分事务split-batch 端点切分最终要落盘到项目的 HTML 文件而这一步是多文件、可能跨多个源文件的复合写入v0.6.91 为此设计了显式的事务边界。前端 runAtomicCutTransaction 发起请求服务端实现在 files.ts 的 split-batch 路由POST /projects/:id/file-mutations/split-batch Body: { files: [{ path, expectedVersion, targets: [...] }], transactionToken } Header: X-Hyperframes-Write-Token服务端处理流程从源码可以完整还原请求校验files必须是非空数组且每项含path、expectedVersion与切分目标否则返回 400路径安全与去重每个path必须能解析到项目目录内resolveWithinProject越界返回 403forbidden path重复路径返回 400乐观并发控制逐文件读取磁盘内容并计算fileContentVersion与请求中的expectedVersion不一致时立即返回409并附带currentVersion与currentContent供客户端诊断。前端对 409 专门给出Cut conflict的错误前缀懒解析后的二次复核由于 GSAP 脚本解析lazy parsing过程中可能让出事件循环上面的解析可能 yield首次写入前重新校验所有基线——服务端会在真正写盘前把每个文件重新读一遍current ! before即再次 409写前备份每个文件写入前调用snapshotBeforeWrite建立备份备份失败则整体中止500写入与回执逐文件writeFileSync后记录recordFileWriteReceipt携带新的内容版本与writeToken若写入过程中抛错则反向回滚已写入的文件written.reverse()逐文件比对当前内容是否已等于写入前版本以处理外部变更GSAP 写回器可配置路由入口处通过resolveGsapWriter({ HYPERFRAMES_GSAP_WRITER: process.env[HYPERFRAMES_GSAP_WRITER] })决定使用recast还是acorn写回器非法取值返回 400。事务令牌transactionToken前端生成形如cut:uuid的值并同步写入X-Hyperframes-Write-Token头则用于把这次服务端写入与 Studio 自身的文件版本追踪打通——前端在发请求前调用markStudioWriteToken(transactionToken)文件监视器收到带该令牌的写入回执时不会把它误判为外部修改从而避免自己切了自己却触发冲突提示。历史记录与失败回滚前端事务封装runAtomicCutTransaction在拿到服务端committed结果后还要完成两步收尾登记历史把每个文件的{before, after}快照以标签如Split timeline clip或Split N clips at X.XXs和kind: timeline交给recordEdit写入 Studio 历史若登记失败会调用rollbackUnrecordedCut按相反顺序把每个文件写回before内容——回滚本身失败时抛出AggregateError提示切分历史失败且无法安全恢复被外部修改的文件避免留下改动已落盘但不可撤销的静默状态同步刷新更新各文件的本地版本观测并触发预览重载SDK 会话重载 preview reload同步失败不会撤销切分而是返回syncFailed: true由 UI 提示用户手动重载。这解释了官方时间线文档中的一句使用说明timeline 指南移动、修剪、分割和许多其他时间线动作都参与 Studio 历史结果不符合预期时立即 Undo 再用更近的缩放或更精确的播放头位置重试——分割动作是一等公民的 history entry而非不可撤销的裸文件写入。修复项GSAP drag intercept 默认关闭v0.6.91 的 Fixes 部分将GSAP drag intercept置于 feature flag 之后、默认禁用。从源码结构看Studio 的画布上有一套完整的 GSAP 拖拽提交链路gsapDragCommit/gsapDragPositionCommit/gsapResizeIntercept/gsapRuntimeBridge等 hooks见 hooks 目录用于把用户在预览中对元素的拖拽/缩放直接翻译成 GSAP 属性写入。这条链路对正在动画中的元素即拖拽瞬间元素位置由 tween 驱动而非静态 CSS 值的处理是历史 bug 高发区拖拽拦截器若不感知当前 tween 的插值状态就会把动画中间值当成元素真实位置提交产生跳变。该版本的做法是把这条拦截路径整体挂到 feature flag 上、默认关闭让回归行为回到稳定的直接提交路径gsapParser 的测试注释 也能看到配套的解析约束位置 tween 不会并入混合属性组否则 drag-intercept 无法解析它。需要注意的前提这一修复只影响 Studio 编辑器内对动画元素的直接拖拽交互不影响渲染引擎packages/producer、packages/engine的渲染结果——渲染路径不经过编辑器拖拽链路因此已产出的项目与渲染行为不受此 flag 影响。内部重构与测试覆盖Internal 一项抽取共享时间线组件、去重为 Razor 工具铺平了结构时间线编辑回调集中在 useTimelineEditCallbacksrazor 的交互层独立为TimelineRazorInteraction组件与拖拽手势处理timelineClipGestureHandlers分离。版本行为有成套测试守护razorSplitTransaction.test.ts意图去重、嵌套坐标重基、409 冲突路径、写令牌消费useRazorSplit.test.ts 与useRazorSplit.history.test.tsxHook 层面的单片段/批量分割、录制中禁编、历史记录登记files.test.ts服务端split-batch端点的请求/响应契约gsapParser.test.ts 同目录的解析测试GSAP 脚本切分与属性分组的正确性。小结与适用前提v0.6.91 的核心价值在于把分割时间线片段从一个危险的裸文件编辑变成了引擎级的一等动作关注点v0.6.91 的实现分割资格显式可判定未锁定、显式时序、稳定标识、合法速率与时长且切点距边界 ≥ 0.03stimelineElementSplit.tsGSAP 动画保留按 ID 选择器精确 retarget逆序处理防 ID 漂移跨越切点的 keyframes 动画不强行拆分而是显式跳过并告警gsapParser.ts多文件一致性服务端split-batch原子事务乐观锁 409、写前备份、失败反向回滚、事务令牌打通文件监视器files.ts可撤销性每次分割登记为 timeline 类 history entry历史登记失败自动回滚文件内容razorSplitTransaction.ts拖拽稳定性GSAP drag intercept 移入默认关闭的 feature flag规避动画元素上的异常拖拽提交适用前提以上能力作用于Studio 编辑场景packages/studio前端 packages/studio-server的 split-batch 端点且分割对象需满足前文的可分割条件对于纯渲染管线CLI / producer / engine 侧的合成与输出该版本未引入行为变化。若你的项目依赖编辑器内拖拽动画元素升级后建议留意 drag intercept flag 的默认值变化对交互手感的影响。【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价