HyperFrames 两阶段相机光标跟踪:为 OpenMontage 打造打字驱动的确定性虚拟镜头
HyperFrames 两阶段相机光标跟踪为 OpenMontage 打造打字驱动的确定性虚拟镜头【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage导读本文讲解 OpenMontage 仓库中 HyperFrames 动画技能库的原子规则camera-cursor-tracking完整规则原文。当一条水平增长的文案不断键入的搜索框、渐次出现的超长 URL需要始终完整地呈现在镜头内时这条规则通过静态锚定 → 追踪跟随的两阶段虚拟相机把视口锁在一个持续移动的焦点光标、高亮块、最后一个键入字形上。读完后你将掌握两阶段偏移量的数学表达与无缝切换、可在多 worker 并行渲染下保持确定性的 GSAP 时间轴写法、全套可调参数从光标几何到相机缓动的取值策略以及如何将这条规则与同仓库的上下文光标、离散文本序列等规则组合成多阶段镜头。HyperFrames 是 OpenMontage 内置的用 HTML 渲染视频技能体系——一条可渲染合成composition就是一份用data-*属性声明时序的 HTML 文件见 hyperframes 入口技能。camera-cursor-tracking属于 hyperframes-animation 下的原子运动规则rules与多阶段相机、坐标定点缩放等规则并列可被诸如 cursor-ui-demo 蓝图 这样的多阶段场景模板引用为主相机。它是如何工作的两阶段虚拟相机核心思路是把两个空间彻底分离World Space世界空间——完整的目标元素包含全部内容Screen Space屏幕空间——视口viewport观众实际看到的窗口。相机在两个阶段之间切换Phase 1静态世界容器停在固定的初始偏移INITIAL_OFFSET上相机不动。这样在追踪开始前观众视线已被锚定在当前构图镜头不会抢戏。Phase 2追踪当焦点光标、高亮、最后键入的字形越过目标屏幕位置——例如可配置的比例CURSOR_TARGET_FRACTION × viewportWidth——世界容器向左平移x: -delta把焦点始终钉在那个屏幕位置上。相位边界处的连续数学两阶段的交接在数学上是连续的——追踪启动的瞬间世界位置恰好等于静态阶段该有的值因此过渡无跳变。代码中使用的分段形式是finalWorldX Math.min(INITIAL_OFFSET, trackingOffset)其中INITIAL_OFFSET是静态阶段的值trackingOffset是让焦点保持在CURSOR_TARGET_FRACTION × viewportWidth所需的任何位移。只要焦点还没有长过目标屏幕 XtrackingOffset就会大于INITIAL_OFFSET它是一个不那么负的数于是Math.min返回静态值一旦焦点越过目标trackingOffset反超INITIAL_OFFSET追踪接管。强调这条规则的作者明确警告不要改成if (typingProgress threshold)的硬分支——硬分支会在相位边界造成可见的镜头跳动见下文关键约束。合成Composition骨架HTML 与data-*属性场景根元素声明了整个合成的时序元数据。data-composition-id是关键——它必须与动画注册表键window.__timelines[tracking-scene]一致data-duration是渲染时长而非时间轴长度data-attributes 参考。div classscene idtracking-scene >.scene { position: relative; width: 100%; height: 100%; } .viewport { position: absolute; inset: 0; overflow: hidden; /* clip the world content */ display: flex; align-items: center; justify-content: flex-start; padding-left: VIEWPORT_PAD_LEFT; /* left margin — variation: left-aligned init */ } .world { display: flex; align-items: center; white-space: nowrap; /* keep text on one line for camera-tracking */ transform: translateX(0); /* GSAP will animate this */ } .search-bar { font-family: {font}; font-size: BAR_FONT_SIZE; font-weight: BAR_FONT_WEIGHT; color: {textColor}; letter-spacing: BAR_LETTER_SPACING; } .search-bar .text { /* Width grows as more characters reveal */ display: inline-block; overflow: hidden; vertical-align: bottom; } .search-bar .cursor { display: inline-block; width: CURSOR_WIDTH; margin-left: CURSOR_GAP; background: {accentColor}; height: CURSOR_HEIGHT_EM; vertical-align: bottom; /* No animation: blink CSS keyframe here — HF renders by seeking a paused timeline, and CSS animation clocks are NOT synced to that seek. A CSS blink will flicker non-deterministically. Drive cursor blink as a finite yoyo tween on the GSAP timeline instead — see GSAP Timeline section. */ }两个要点CSS 注释本身就是约束文档——.cursor上禁止 CSSkeyframes闪烁动画因为 HyperFrames 通过寻找已暂停时间轴的方式渲染每一帧CSS 动画时钟与该 seek 不同步必然闪屏。闪烁必须由 GSAP 时间轴上的有限 yoyo tween 驱动。VIEWPORT_PAD_LEFT必须与.viewport的padding-left严格一致否则相机数学会漂移见下文参数表。驱动GSAP 时间轴整条规则围绕一条暂停的 GSAP 时间轴展开且必须在脚本解析期同步构建script srchttps://cdn.jsdelivr.net/npm/gsap3.14.2/dist/gsap.min.js/script script window.__timelines window.__timelines || {}; const tl gsap.timeline({ paused: true }); // Pre-measure target text width to compute tracking distance. // Build the timeline SYNCHRONOUSLY — see Critical Constraints for why // a fonts.ready gate causes worker-race flicker. const textEl document.getElementById(reveal-text); const fullText textEl.textContent; const targetCursorScreenX CURSOR_TARGET_FRACTION * VIEWPORT_WIDTH; const fullWidth textEl.scrollWidth; // total text width after full reveal const trackingDelta Math.max(0, VIEWPORT_PAD_LEFT fullWidth - targetCursorScreenX); // Phase 1 — text reveals progressively; camera holds. // Reveal via max-width tween (no layout-property tweens on width/left/top). tl.fromTo( .search-bar .text, { maxWidth: 0 }, { maxWidth: fullWidth, duration: REVEAL_DUR, ease: none, // linear typing rate }, REVEAL_START, ); // Phase 2 — camera tracks. Begin BEFORE full reveal so the boundary feels // continuous (text is still revealing as camera starts moving). The // Math.min(INITIAL_OFFSET, trackingOffset) formulation makes the handoff // mathematically continuous; see How It Works. tl.to( .world, { x: -trackingDelta, duration: TRACK_DUR, ease: power2.inOut, }, TRACK_START, ); // Cursor blink — GSAP-driven (NEVER CSS keyframes infinite, which doesnt // sync with HFs seek-by-frame). Finite yoyo, repeats computed from scene // length so blinks land deterministically across frames. const blinkRepeats Math.ceil(SCENE_DURATION / BLINK_HALF_PERIOD) - 1; tl.to( .search-bar .cursor, { opacity: 0, duration: BLINK_HALF_PERIOD, ease: steps(1), // hard on/off, no fade yoyo: true, repeat: blinkRepeats, }, 0, ); window.__timelines[tracking-scene] tl; /script时间轴的三段编排解读文本揭示Phase 1fromTo把.text的maxWidth从 0 动画到fullWidth。选择maxWidth而非width是有意为之——这是动画属性白名单内的布局安全做法ease: none保证每字符击键节奏线性任何缓动都会扭曲打字手感。文本宽度用textEl.scrollWidth预测量而不是字符数 × 字号。相机追踪Phase 2tl.to(.world, { x: -trackingDelta, ...})在TRACK_START位置插入。位移量在开头一次性算出trackingDelta Math.max(0, VIEWPORT_PAD_LEFT fullWidth - targetCursorScreenX)即文本总宽度超出目标光标屏位的那一部分。追踪刻意在揭示完成之前开始TRACK_START REVEAL_START REVEAL_DUR让相位切换像一次自然的跟拍。光标闪烁有限 yoyo tween。repeat次数由场景长度推导Math.ceil(SCENE_DURATION / BLINK_HALF_PERIOD) - 1保证闪烁能确定性地覆盖整个场景长度、不跨帧错乱ease: steps(1)提供硬开关无淡变。这段时间轴完整符合 HyperFrames 的动画运行时契约determinism-rules暂停时间轴、注册键与data-composition-id一致、绝不在async/Promise/setTimeout中构建、使用有限 repeat 而非repeat: -1。变体Variations三种已验证的配置模式覆盖了最常见的取景需求居中 → 中心追踪Centered → Center-Tracked设置.viewport { justify-content: center; padding: 0; }。一旦焦点越过中线CURSOR_TARGET_FRACTION 0.5相机即开始追踪。左对齐 → 右追踪Left-Aligned → Right-Tracked即上文完整示例。最适合内容一开始就超过视口宽度的场景如整段 URL 渐现。连续打字驱动器Continuous typing driver用onUpdate打字时钟charsTyped Math.floor(progress)替代maxWidthtween并在每一帧用measureNodeWidth驱动光标屏幕 X。当键入文本在场景别处被消费时例如被父级条带的相机偏移量读取必须使用此变体。参数选择指南规则附带了完整的参数取值表逐条给出取值范围、效果与约束。下面是整理后的速查表可作为落地时的默认参考参数范围 / 取值效果与约束VIEWPORT_PAD_LEFT0 → 约视口宽度的 10%Phase 1 锚定 X。0 贴左边缘越大越像居中的 hero 取景。必须与.viewport的padding-left一致否则相机数学漂移VIEWPORT_WIDTH合成的data-widthCSS 像素必须等于场景根的data-width永不被 tweenCURSOR_TARGET_FRACTION0.5中心追踪→ 0.75右倾光标后有更多已揭示文本取值越低画面内已揭示文本越少越高则追踪启动越晚BAR_FONT_SIZE约为视口高度的 8%–12%低于约 6% 时读感会从电影化元素降级为UI 控件BAR_FONT_WEIGHT离散400中性演示文本/ 700hero/标题取景按叙事层级选择BAR_LETTER_SPACING轻微负值更紧致、更电影化→ 0默认条内字距微调CURSOR_WIDTH约 4–10 px1080p更细读作输入光标更粗读作块状插入符CURSOR_GAP数 px 呼吸空间不要超过光标宽度否则视觉上会脱开CURSOR_HEIGHT_EM0.85–1.0匹配所打字形的视觉高度REVEAL_START通常为 0若前面还有其他相位需 ≥ 该相位结束 小缓冲REVEAL_DUR随字符数缩放单字符节奏约 0.05–0.15 s必须早于SCENE_DURATION结束最好与追踪相位略有重叠TRACK_START通常早于揭示完成若焦点在 t0 已越过目标可等于REVEAL_STARTTRACK_START REVEAL_START REVEAL_DUR保证平滑交接TRACK_DUR0.8–2.0 s低于 0.5 s 读作跳动snap超过 2.5 s 拖沓SCENE_DURATION必须等于场景根的data-duration喂给闪烁 repeat 计数不匹配会导致闪烁提前截断或越界BLINK_HALF_PERIOD0.2–0.4 s0.3 s 最接近自然光标闪烁派生值Math.ceil(SCENE_DURATION / BLINK_HALF_PERIOD) - 1必须 ≥ 0缓动选择离散相机平移用power2.inOut/power3.inOut营造电影化落定避免back.out过冲像 UI 弹跳而非相机。揭示用none线性闪烁用steps(1)硬开关关键原则用getBoundingClientRect()/ 探针节点测量宽度不要用字符数 × font-size推算——比例字体各字形宽度不同。世界容器.world必须white-space: nowrap——文本必须保持单行相机数学才成立。预分配世界宽度把maxWidth设为满目标宽度避免 tween 中途出现布局位移。相机用缓动而非直线运动power2.inOut/power3.inOut才像自然运镜。用缓动近似弹簧感GSAP 没有内建弹簧但back.out(${BOUNCE_FACTOR})或power4.out可以近似落定回弹的手感。关键约束Critical Constraints这部分是全规则最容易踩坑的地方值得逐条展开时间轴必须同步构建、禁止fonts.ready门控。HyperFrames 在并行 worker 中渲染各帧每个 worker 都是全新浏览器。如果把时间轴构建包在document.fonts.ready.then(...)里部分 worker 会在 Promise 解析前就 seek 帧找不到已注册的时间轴 → 该帧以 CSS 初始态渲染例如max-width: 0导致文本为空另一些 worker 则正常 → 空/满文本之间出现可见闪烁。应在脚本解析期就把window.__timelines[id] tl注册好——即便字体尚未加载。相机数学能容忍回退字体测量造成的几个百分点的宽度误差但 worker 竞争闪烁不可接受。如果确实需要精确的字体加载后测量在 tween 的onUpdate里重测对逐帧 seek 仍然是确定性的而不是用 Promise 门控或者在font-face上设font-display: block强制浏览器在绘制任何文本前等待字体。时间轴必须暂停gsap.timeline({ paused: true })永远不调用tl.play()。这正对应 determinism-rules 中动画状态必须可由 HyperFrames 时间 seek的核心要求——渲染器没有播放概念每帧都是对时间值的一次全新 seek。注册表键 data-composition-idwindow.__timelines[tracking-scene]必须与场景根匹配。相位边界处数学必须连续追踪启动瞬间世界的x必须等于静态阶段的值。Math.min(INITIAL_OFFSET, trackingOffset)保证这一点不要改成if (typingProgress threshold)硬分支否则镜头会可见跳变。光标内联而非绝对定位光标必须是文本的兄弟元素inline-block随文本流自然跟随——绝对定位会与相机数学错位。.viewport必须overflow: hidden裁剪世界左边缘在平移出画时的泄漏。光标闪烁必须由 GSAP 驱动禁止 CSSkeyframes ... infiniteHyperFrames 靠 seek 暂停时间轴渲染CSS 动画时钟与 seek 不同步任何 CSS 驱动的闪烁都会跨帧不确定地闪屏。闪烁永远是暂停时间轴上的一次有限 yoyo tweenrepeat 次数由场景长度计算得出。这些约束与 hyperframes-animation SKILL 中布局常量预计算、空间运动只用 GSAP transform 别名x/y/scale/rotation、布局属性width/height/top/left不得用于 layout 变更 tween的动画工艺约定一脉相承——本规则用maxWidthx正是对这一约定的具体落实。组合与配对技能这条规则设计成可与同目录其他原子规则自由组装context-sensitive-cursor.md在打字过程中按文本片段切换光标颜色/样式——把相机追光标与光标随片段变色叠起来就是完整的终端/搜索框叙事镜头discrete-text-sequence.md非线性文本揭示错别字、整段补充、停顿、思考间隙与连续打字 相机追踪的平滑型形成互补。技能级配对则对应 HyperFrames 的领域技能分工/hyperframes-animation—— 时间轴 tween API原子规则本库/hyperframes-core—— 合成接线与data-*属性data-attributes.md/hyperframes-cli—— 用hyperframes lint校验注册表键与时长lint-validate-inspect。在蓝图中的角色从 cursor-ui-demo 蓝图 的引用可以印证它的典型用法当需要一个相机伺服到光标所触之处的产品演示Product_Intro / Key_Feature 变体时camera-cursor-tracking被列为主相机primary——这种对光标的相机伺服正是该蓝图区别于无手势相机滚动与静态设备窗口导览的核心。它通常与viewport-change横移/竖移/推拉的形式实现、multi-phase-camera把追逐拆成离散交互节拍以及coordinate-target-zoom推近到被操作区域一起组成完整的chase运镜链。落地检查用 CLI 验证合成在完成 HTML/CSS/GSAP 组装后HyperFrames 提供一套先检查后渲染的纪律hyperframes-cli对运动密集的场景尤其适用npx hyperframes lint # 静态检查data-composition-id 缺失、同轨重叠、未注册时间轴 npx hyperframes validate # headless Chrome 运行时检查console 错误、网络失败、WCAG 对比度 npx hyperframes inspect --samples 15 # 时间轴布局扫描默认 9 个采样点 npx hyperframes snapshot --frames 10 # 输出关键帧 PNG 供肉眼核对lint能直接抓到本文反复强调的注册表键与data-composition-id不匹配时长不一致这类缺陷inspect支持*.motion.jsonsidecar 做运动意图断言如appearsBy、staysInFrame是渲染 ≠ 预览类 bug 最接近自动化的代理检查。对打字揭示这类逐帧 seek 场景先跑一轮lintsnapshot再看 PNG通常比直接渲染视频更快收敛。小结camera-cursor-tracking给出了一个把镜头语言落实为确定性的两阶段数学模型的完整范例静态锚定稳住视线、Math.min让追踪接手时无缝衔接、暂停时间轴 同步注册保证多 worker 并行渲染下逐帧可复现。它的可迁移价值在于——任何水平增长元素 移动焦点的镜头问题搜索框打字、URL 渐现、跑马灯焦点跟拍都能复用这套 HTML/CSS/GSAP 骨架与参数调优方法再通过上下文光标变色与离散文本序列等规则把单镜头扩成多段落的多阶段叙事。【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考