- AI 技能
- 媒体生成
- 视频
【免费下载链接】video-shotcraft
AI video skill for Claude Code & Codex — cinematic product videos with Remotion: 152 shot recipe cards, 209 motion previews, a production-ready template
本篇技术文章围绕 Video Shotcraft 镜头配方库中的 steep-tilt-glide 镜头卡 展开:如何在 Remotion 中用纯 CSS 3D 变换复刻"镜头钉死、页面自己滑"的侧掠运镜,涵盖相机常数标定、滑移曲线设计、速度重影、悬空贴落与由暗揭亮的完整实现细节。读完后你可以直接按参数表复现该镜头,并理解每一处参数背后的取舍判例(哪一版被裁、为什么裁),把这张卡真正用到长页面 UI 巡览段落中。
镜头卡定位:一镜 4 秒的"侧掠"机位
卡片 frontmatter 给出了这张镜头卡的定位:
| 元信息 | 值 |
|---|---|
| 一句话 | 固定镜头下直立页面以 60° 强透视侧立(右近左远),页面自身沿其 3D 横面方向滑移掠过镜头(物动镜不动),滑移带速度重影、文字组件悬空贴落、由暗揭亮 |
| 适用 | 长页面/多区块 UI 的炫技巡览(内容依次滑过固定机位);暗场霓虹调性;与贴面运镜卡互补的"侧掠"机位 |
| 时长 | 4s(120f)单镜;页面越宽越可拉长 |
| 能量 | 中高(透视炫技+持续运动,但节奏是匀的) |
| 标签 | ui-entrance |
它和 graze-face-tour(贴面游走特写)来自同一原片(clickup-30.mp4)的复刻需求,但两者在运动模型上完全对偶:贴面卡是"镜头动、页面静",侧掠卡是"镜头静、页面动"。这个分工不是风格偏好,而是被评审判例强制的——参考实现文件头的注释逐字记录了用户意见:"角度不对……镜头是固定的,但是页面本身往其3d空间的横面方向移动的效果"(见 SteepTiltGlide.tsx 第 1–8 行注释)。
命门:物动镜不动——为什么镜头必须全程常数
这张卡的意图描述非常直接:把页面立成一堵斜墙,镜头钉死不动,60° 强透视让右缘贴脸、左缘消失在灭点,页面自己沿自身平面横向滑移——内容像列车车厢一样依次掠过。
从源码结构看,这个"镜头不动"被实现为一个全程不变的相机常量对象(SteepTiltGlide.tsx#L259):
// v7(批次 16):用户意见"改成60度吧"——rotY -53 → -60(精确定值) const CAM = { persp: 1100, // perspective 距离 origin: '30% 58%', // perspectiveOrigin(灭点偏左中下) rotY: -60, // 主倾斜角,右缘近、左缘远 rotZ: -2, // 轻微侧倾,顶缘向右上扬 left: '40%', top: '15%', // 面板在画面中的锚点 scale: 0.62, // 内容坐标 → 画面坐标的缩放 };渲染层级(SteepTiltGlide.tsx#L261-L275)严格分三层,3D 变换只发生在中间层:
const PanelLayer = ({ lx, shade, opacity, t }) => ( <AbsoluteFill style={{ opacity }}> <AbsoluteFill style={{ perspective: CAM.persp, perspectiveOrigin: CAM.origin }}> <div style={{ position: 'absolute', left: CAM.left, top: CAM.top, transform: `rotateY(${CAM.rotY}deg) rotateZ(${CAM.rotZ}deg)`, transformOrigin: 'left top', }}> {/* 页面自身在其 3D 平面内横移(局部 X 轴)——全程唯一动的量 */} <div style={{ transform: `scale(${CAM.scale}) translateX(${lx}px)`, transformOrigin: 'left top' }}> <Panel shade={shade} t={t} /> </div> </div> </AbsoluteFill> </AbsoluteFill> );要点在于:perspective、perspectiveOrigin、rotateY、rotateZ四个量在 120 帧里一个都不变,唯一随帧变化的量是内层 div 的translateX(lx)——而且这个位移发生在页面自身的局部坐标系里,所以它滑移的方向永远是"页面 3D 横面方向",而不是屏幕方向。卡片参数表把这个运动模型标为"命门":早期"镜头横移版"被评审打回,打回意见就是文件头引用的那句"镜头是固定的,页面本身往其3d空间的横面方向移动"。镜头一动,语义就从"页面在自我展示"变成了"运镜炫技",镜头语言完全不同。
角度:经三轮收敛的精确 -60°
透视角不是"好看就行"的自由参数,而是这张卡的身份:
- v5 起步时对照原片密帧校准为 rotateY(-38°)(源码注释中保留了当时的校准记录);
- 后续批次用户裁定 -45°"不对" → -53°"再大一点" → 最终精确定值 -60°(v7);
- 参数表同时给出边界:-70° 以上内容就读不清了。
因此实战微调范围建议55–65°,"出这个区间等于换手法"。
滑移模型:超宽面板 + 尾段斜率不归零的缓动
页面本体是一个超宽面板,内容坐标6200×2400(SteepTiltGlide.tsx#L76-L79)。为什么必须这么宽?参数表给了解释:页面宽度需 ≥3 屏内容——太窄滑两秒就完,撑不起一镜。参考实现里页面分两段:左段是 ClickUp 3.0 的顶栏+侧栏,右段是 Product Management 任务列表("原片尾程满幅白页"),整页手绘了 tab 条、logo 行、药丸框、任务行等约二十个组件,成本不低——这也是"已知坑"里提醒"实战可用真实长截图代替(滑移逻辑不变)"的原因。
滑移曲线是这段镜头的第二命门,实现只有三行(SteepTiltGlide.tsx#L282-L286):
// 起步柔和后近匀速,全程不安定停死(bezier 尾段斜率不归零) const glide = Easing.bezier(0.3, 0.12, 0.72, 0.9); const lxAt = (f: number) => { const p = interpolate(f, [0, 120], [0, 1], { easing: glide, extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); return interpolate(p, [0, 1], [60, -4100]); // 局部 X:+60 → -4100 };设计意图拆开看:
- 120 帧内局部 X 从+60 滑到 -4100,内容从镜头右侧一直掠过到左侧;
bezier(0.3, 0.12, 0.72, 0.9)的形状:起步柔和(有呼吸感)→ 中段近匀速 → 尾段斜率不归零。尾段不归零意味着镜头切走时页面仍在运动,"不安定停死";- 参数表记录了两个反例:早期版本"中途停死被抓(v4 返工)",全用 linear 则"少了起步呼吸"。也就是说这条 bezier 是在"全程匀速"和"中途刹停"两个失败样本之间收敛出来的。
速度重影:用"过去时刻的重投影"做拖尾
滑移带速度重影是该镜头的第三个签名。实现思路不是画模糊拖尾,而是把整个 PanelLayer 再渲染两次,位置取过去时刻的局部位移,透明度与当前滑移速度成正比(SteepTiltGlide.tsx#L291-L294、L307-L311):
// 重影强度 ∝ 滑移速度(拖尾=过去时刻的局部位置,同一固定镜头下重投影) const speed = Math.abs(lxAt(frame - 1) - lxAt(frame + 1)) / 2; const g1 = Math.min(0.42, speed * 0.03); const g2 = Math.min(0.22, speed * 0.016); ... // 重影(先画,垫在本体后面;位置=过去时刻的页面局部位移) {g2 > 0.02 && <PanelLayer lx={lxAt(frame - 5)} shade={shade} opacity={g2} t={drop} />} {g1 > 0.02 && <PanelLayer lx={lxAt(frame - 2.5)} shade={shade} opacity={g1} t={drop} />} {/* 本体 */} <PanelLayer lx={lx} shade={shade} opacity={1} t={drop} />三层用同一个PanelLayer组件,相机常量完全一致,所以拖尾层就是"过去时刻的页面在同一固定镜头下的重投影"——几何上严格正确,不会出现拖尾与本体透视对不上的问题。两个细节值得注意:
- 透明度 ∝ 速度,且用
min(0.42 / 0.22, ...)封顶:双层拖尾(f-2.5 浓、f-5 淡)只有速度快时才出现,静止或慢速时g < 0.02直接不渲染。参数表里"恒定重影读作重像故障"说的就是反例——重影必须跟着速度呼吸; - 重影先画、本体后画,利用 DOM 层叠垫在身后。
悬空贴落:FloatWrap 同形软影 + 错峰行程
页面内的文字组件不是原地出现,而是悬浮在界面上空、带同形软影,随滑移进程错峰贴落——这是从 graze-face-tour 卡继承的"巡礼语法"。实现是两个小函数:
const easeFall = Easing.bezier(0.5, 0.05, 0.6, 1); // 加速贴落、末端软着陆 const FloatWrap = ({ h, children }) => ( <div style={{ position: 'relative' }}> {h > 2 && ( <div style={{ // 同形软影:随高度抬移、放大、变糊变淡 position: 'absolute', inset: 0, transform: `translate(${h * 0.24}px, ${h * 0.46}px) scale(${1 + h * 0.0009})`, filter: `blur(${5 + h * 0.075}px) brightness(0.35) saturate(0.4)`, opacity: Math.min(0.38, 0.15 + h * 0.0016), }}>{children}</div> )} <div style={{ transform: `translate(${-h * 0.34}px, ${-h * 0.8}px)` }}>{children}</div> </div> ); /* land=贴合完成时刻(0..1),之前从 H 高度加速贴落 */ const liftOf = (t, land, H = 230) => { const FALL = 0.32; const p = Math.min(1, Math.max(0, (t - (land - FALL)) / FALL)); return (1 - easeFall(p)) * H; };关键参数与语义:
- 悬浮高度 H 取 230–280px(
liftOf的第三参在 230/240/250/260/280 之间变化),高于 graze-face-tour 卡的 120–180px,因为 60° 侧立视角下要读得出悬浮,抬得更高; - 影子参数全部随高度线性收敛:blur、offset、opacity 都是
h的函数,落地时(h→0)影子与本体重合消失——"影子是可感命门"(见 graze-face-tour 卡的论述); - 错峰贴落:面板内各元素传入不同的
land时刻(0.3、0.42、0.54、0.62、0.68、0.8、0.86…),按滑移方向排布"先经过的先落",全程并行重叠而非串行等待——参数表注明这是"巡礼语法",与整体登场镜"全体同帧齐落"的语法(neon-frame-orbit-drop 判例)互斥,选错语法两边都会被裁; - 进程驱动量是
drop = interpolate(frame, [4, 100], [0, 1]),即贴落进程占满全片前 100 帧。
由暗揭亮与暗场氛围
最后一层签名是"揭亮":起始面板压暗,约 1.4s 内揭到全亮,左缘额外渐变阴影同步退去(SteepTiltGlide.tsx#L243-L250、#L289-L298):
// 面板黑罩:起始暗 → 约 1.4s 全亮(对齐原片节奏) const shade = interpolate(frame, [0, 18, 44], [0.7, 0.4, 0], { extrapolateRight: 'clamp' }); ... // 面板内黑罩(随进度揭亮) <div style={{ position: 'absolute', inset: 0, background: '#050409', opacity: shade }} /> // 左缘额外阴影(起始更暗,随亮度一同退去) <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(90deg, rgba(5,4,9,0.9), rgba(5,4,9,0) 42%)', opacity: Math.min(1, shade * 1.6), }} />配合#060409的近黑底色、左侧径向紫晕(glow在 14→70 帧从 0.15 涨到 1)和全屏暗角,构成卡片所说的"暗场霓虹调性"。注意黑罩是画在面板内部的(Panel 组件的最后一层),所以它跟着页面一起滑移——暗是"页面还没亮起来",而不是镜头前的遮罩,这一点在复刻时容易做错层级。
参数表:完整继承与调节手感
以下是原卡片的完整参数表(含评审判例),实战调参时优先对照"调节手感"列:
| 参数 | 典型值 | 调节手感 |
|---|---|---|
| 倾斜角 | rotateY-60°(用户精确定值) | -45° 被裁"角度不对"、-53°"再大一点";>-70° 内容读不清 |
| 运动模型 | 镜头全常数、页面自移 | 命门——镜头横移版被裁"镜头是固定的,页面本身往其3d空间的横面方向移动" |
| 滑移曲线 | 尾段斜率不归零的 bezier | 中途停死被抓(v4 返工);全 linear 少了起步呼吸 |
| 重影 | 透明度∝速度,双层 f-2.5/f-5 | 恒定重影读作重像故障 |
| 页面宽度 | ≥3 屏内容 | 太窄滑两秒就完,撑不起一镜 |
| 悬空贴落 | 错峰(巡礼语法)+ 影随高度 | 语义判例见 graze-face-tour/orbit-drop 三卡族 |
补充源码中的完整取值,方便直接复用:
| 参数 | 值 | 源码位置 |
|---|---|---|
| perspective | 1100 | CAM 对象 |
| perspectiveOrigin | 30% 58% | 同上 |
| rotateY / rotateZ | -60° / -2° | 同上 |
| transformOrigin | left top(旋转层与位移层均取左上锚点) | PanelLayer |
| 面板锚点/缩放 | left 40%、top 15%、scale 0.62 | 同上 |
| 面板尺寸 | 6200 × 2400(内容坐标) | PW/PH |
| 滑移范围 | 局部 X +60 → -4100,120 帧 | lxAt |
| 滑移缓动 | bezier(0.3, 0.12, 0.72, 0.9) | 同上 |
| 重影层 | f-2.5(上限 0.42×speed×0.03)/ f-5(上限 0.22×speed×0.016) | SteepTiltGlide.tsx#L291-L294 |
| 悬浮高度 H | 230–280px | liftOf 调用点 |
| 贴落窗口 | 每元素落地前 0.32 归一化进程内完成 | liftOf |
| 揭亮 | shade 0.7→0.4→0 @ 帧 0/18/44 | SteepTiltGlide.tsx#L290 |
已知坑与实战边界
原卡片"已知坑"一节全部继承如下,并用源码做了对应标注:
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,首次实战须以真实素材回验。参考实现本身就是手绘占位 UI(双 V 叠形 logo、蓝四菱形 glyph 都是近似),这一点在 SteepTiltGlide.tsx#L40-L74 的注释里写得很明白;
- 角度判例:透视角是本卡身份,实战微调范围建议 55–65°,出这个区间等于换手法;
- 与 graze-face-tour 的分工:那张镜头动(贴面游走)、页面静;本卡镜头静、页面动。同段落二选一,不能混用;
- 与 runway-ground-skim 的分工:那张是俯角平躺+垂直贴落+立起;本卡是侧立+横向滑移,轴向正交;
- 超宽页面手绘成本高,实战可用真实长截图代替(滑移逻辑不变)——因为滑移只作用于
translateX(lx)一个量,页面内容换成<img>不影响运动模型。
与其他镜头卡的族谱关系
从卡片库结构看,这张卡与几张卡构成明确对偶或互补关系,选型时按"谁在动"判断:
- 对偶于 graze-face-tour(贴面游走:镜头动、页面静,戏眼是文字空中贴落)——两者共享
FloatWrap悬浮软影模式(本卡文件注释第 17 行即标注"GrazeFaceTour FloatWrap 模式"),但运动模型相反; - 对偶于 runway-ground-skim(俯角平躺、垂直贴落、最后立起)——与本卡轴向正交;
- 与 tension-camera-moves / space-camera-moves 的差别在 graze-face-tour 卡中被概括为"那两张是相机语言本身,本族卡是机位+元素行为的组合签名"——steep-tilt-glide 同理,签名是"固定强透视机位 + 页面自移 + 速度重影"的绑定,去掉任何一半都应改用对应的基础相机卡。
参考资料
- 镜头卡原文:references/shots/camera/steep-tilt-glide.md
- 参考实现:demos/camera/steep-tilt-glide/SteepTiltGlide.tsx(Workbench 工作区同步副本:workbench/demosrc/camera/steep-tilt-glide/SteepTiltGlide.tsx)
- 对偶镜头卡:references/shots/camera/graze-face-tour.md
- 卡库 API 与检索入口:gallery/api/library.json、gallery/llms.txt
原片出处为 clickup-30.mp4(卡片 frontmatter 与 graze-face-tour 卡共同标注)。需要说明的是,demo 与卡片参数基于占位素材调校通过,属于"调校起点"而非实战定稿;真实项目落地时请按上述"已知坑"以实际素材回验角度、滑移幅度与揭亮节奏。
- AI 技能
- 媒体生成
- 视频
【免费下载链接】video-shotcraft
AI video skill for Claude Code & Codex — cinematic product videos with Remotion: 152 shot recipe cards, 209 motion previews, a production-ready template
相关推荐
OpenUI 完整上手:从一句描述到实时渲染的 UI 组件
OpenUI 完整上手:从一句描述到实时渲染的 UI 组件 输入“做一个响应式定价页”,几秒后一个实时渲染的 UI 就出现在屏幕上,你还能继续提修改意见,或把它
AI 技能媒体生成视频Video2X 实用指南:AI 视频超分与帧插值,四引擎选型到完整跑通
Video2X 实用指南:AI 视频超分与帧插值,四引擎选型到完整跑通 Video2X 是一个用 C/C++ 编写的开源 AI 视频超分与帧插值框架,整合了 A
AI 技能媒体生成视频video-shotcraft 镜头配方 list-stack-press:用 Remotion 做出"有重量"的列表堆叠镜头
video shotcraft 镜头配方 list stack press:用 Remotion 做出"有重量"的列表堆叠镜头 本篇围绕 video shotc
AI 技能媒体生成视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考