ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Oil Motion运行时控制器源码剖析:frame-scrub与分段播放的阻尼、环形距离和反向撤回

Oil Motion运行时控制器源码剖析:frame-scrub与分段播放的阻尼、环形距离和反向撤回 Oil Motion运行时控制器源码剖析frame-scrub与分段播放的阻尼、环形距离和反向撤回【免费下载链接】oil-motion设计并实现随滚动、拖动、指针或状态变化响应的网页动画覆盖素材、时间轴和运行时。项目地址: https://gitcode.com/gh_mirrors/oi/oil-motionOil Motion 是一个把 AI 视频、序列帧变成网页交互动画的开源 Skill它负责生成关键帧、编译时间轴并通过运行时控制器让画面跟随滚动、指针、拖动或状态变化。本文剖析它运行时层的核心源码 assets/interactive-motion.tsframe-scrub 控制器如何用阻尼让画面平滑随动、如何用最短环形距离处理 360° 旋转输入以及 segment-playback 控制器如何实现分段播放与反向撤回。先搞清楚控制器是怎么选出来的Oil Motion 规定“格式选择”和“播放方式”是两件独立的事。预算脚本 scripts/motion_budget.py 根据 Motion Brief 中的time_control直接决定runtime.controller映射关系只有三种见 references/runtime.mdtime_controlruntime.controller行为scrubframe-scrub输入值持续映射到帧位置停手画面停在原地segment-playsegment-playback输入只选择下一状态片段随后按时间自行播放autonomousautonomous-playback时间自行推进交互只负责开始/暂停所有控制器都只读取编译生成的build/timeline.json页面里不允许维护第二份时间常量。本文重点拆解前两个控制器。frame-scrub输入停在哪画面就停在哪createFrameAnimatorinteractive-motion.ts#L66-L158是 frame-scrub 的完整实现对外只暴露四个方法setTarget(frame)直接指定目标帧setProgress(0~1)把一维进度映射到progress × (frameCount - 1)setDirection(x, y)把指针相对主体的方向角映射到帧环上getCurrentFrame()/destroy()读取当前位置、释放动画帧。阻尼为什么快速甩动指针不会闪烁核心在smoothDamp函数interactive-motion.ts#L36-L64它移植了 Unity 经典的 SmoothDamp 算法用omega 2 / smoothTime把平滑时间换算成角频率smoothTime默认0.11秒——值越小跟随越快值越大“拖尾”越明显用maxSpeed限制每帧最大位移默认frameCount × 2即最快约半秒扫完整个时间轴防止指针急甩时帧数瞬间跳变每帧按指数衰减更新速度和位置并在越过目标点时强制落位归零L56-L62保证不会过冲后回弹。渲染全部集中在requestAnimationFrame循环里L95-L124输入事件只更新target不直接碰 DOM当位置与目标差、速度都小于0.002时主动停表符合项目“每个动画帧最多一次 DOM 写入、状态稳定时停止 rAF”的性能验收标准。若系统开启了prefers-reduced-motion则直接跳到目标帧不做连续 scrub。最短环形距离360° 展台自转的关键当素材是闭环动画circular: true如角色圆周注视、产品展台自转不能把帧号当直线处理。wrap与shortestCircularDeltainteractive-motion.ts#L25-L34负责这件事delta wrap(to) - wrap(from) 若 delta 帧数/2 → delta - 帧数 若 delta -帧数/2 → delta 帧数也就是说从第 239 帧转到第 1 帧240 帧图集走的是2 帧的短路径而不是倒退 238 帧。setTarget在环形模式下会把目标改写成position 最短距离L133-L139阻尼系统看到的就是一条连续前进的轨迹。setDirection(x, y, startAngle)L140-L145则把指针极角归一化到0~1再乘帧数默认起始角-0.75π正好对应“指针在下方时角色正视镜头”。一个 240 帧、16 列 15 行的环形图集配置示例可参考 assets/motion-manifest.example.json。segment-playback分段播放与反向撤回“点击下一章节播放转场”这类交互用的是createSegmentPlayerinteractive-motion.ts#L257-L512。它初始化时就严格校验timeline.jsonstart hold endExclusive、各段不重叠、states数量必须等于段数加一、状态 ID 唯一且与from → to顺序一致——任何一项不满足直接抛错不进入“运行时才崩”的状态。正向播放速度曲线属于时间轴每段可声明播放速率曲线SegmentRateCurveL233-L243constant恒定速率edge-mid-edge两端快、中间慢按cos²权重在 edgeRate 与 midRate 之间插值适合“起势快、结尾快”的镜头感。正向播放直接调用video.play()并用requestVideoFrameCallback逐帧监听进度L322-L331不支持时才回退 rAF文档明确禁止用低频的timeupdate判断停帧。反向撤回不依赖负 playbackRate 的手动倒放这是本文件最有巧思的部分。浏览器普遍不支持负playbackRate所以反向时控制器暂停视频、手动逐帧回退currentTimeL428-L438video.pause() video.currentTime max(targetTime, currentTime - rate × delta)围绕这条倒放链路源码做了三层防护边界间隙清除removeBoundaryGapL349-L376在起播和每帧 tick 时检查当前时间是否落在“上一段 hold 与下一段 start 之间”的剪辑缝隙里若是则直接跳过缝隙避免倒放时画面卡在半透明接缝帧上精确落位接近目标frameDuration/2内就调用settleL378-L386——先pause()再把currentTime精确设到目标状态的hold杜绝“越过目标再回跳”快速连续输入可取消每次startRun递增runTokenL388-L390旧的 tick 回调发现 token 不匹配立即退出cancel()则直接作废当前任务并暂停视频。因此用户连点五次“上一步”只有最后一个目标生效这正是 runtime 规范中“快速连续输入只保留最新目标”的落地。step(-1)/step(1)L485-L487把方向增量转成goTo(targetState ± 1)边界状态自动 clamp不会越界出空状态。分步手势把惯性滚轮合并成一步分段播放与分页导航组合时输入侧由 assets/step-gesture.ts 的createStepGestureAdapter统一处理L44-L67累计增量达到threshold默认 40px才触发一次onStep把一次滚轮/触控板惯性序列合并成一个方向意图而不是每个 wheel 事件跳一个状态反向输入立刻清零累计值并解锁方向天然支持“上滚两格再下滚一格”的撤回idleMilliseconds默认 140ms无新输入后自动复位setProgrammaticNavigation(true)可屏蔽页面自身平滑滚动引起的误触发。手势阈值、惯性结束判定只写在这一处媒体控制器和分页组件不得各写一份。接入与验收要点结合 SKILL.md 第 8 步与 references/runtime.md接入两个控制器时记住这几条硬性要求一个持续存在的媒体实例切换状态不替换src、不重建视频节点分段播放的反向永远是“从当前画面撤回”不得换源硬切时间值只来自编译结果hold、start、endExclusive全部取自最终编码帧禁止手工推算反向必须倒放同一段推进变拉远、溶解变复原爆炸、泼洒这类倒放违反直觉的动作要单独生成反向片段降级路径prefers-reduced-motion使用合同指定的静态状态资源失败时回退静态画面并解锁页面。从smoothDamp的过冲防护、到shortestCircularDelta的环形取短、再到反向撤回的 token 取消机制两个控制器共同体现了 Oil Motion 的一条设计原则输入事件只表达意图平滑、落位和取消都交给共享控制器——这也是它的动画在快速反向、连续输入下不粘滞、不抽动、不回跳的原因。更多运行时细节可查阅 references/runtime.md交付格式的选择逻辑见 references/delivery-selection.md。【免费下载链接】oil-motion设计并实现随滚动、拖动、指针或状态变化响应的网页动画覆盖素材、时间轴和运行时。项目地址: https://gitcode.com/gh_mirrors/oi/oil-motion创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表