Remotion 画布裁剪(Cropping)完全指南:通过 cropLeft / cropRight / cropTop / cropBottom 精确控制画面内容
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
在 Remotion 中,视频合成的每个帧本质上都是「React 组件渲染出的画面」。当需要隐藏画面边缘、裁掉多余内容、或在画布编辑器(Canvas)中交互式地调整一张图片、一段视频或一个<Sequence>的可见范围时,最推荐的方式不是手写clipPath,而是使用统一的裁剪属性组cropLeft、cropRight、cropTop、cropBottom。本文围绕 remotion-markup/cropping 的说明展开,结合packages/core的裁剪实现源码与测试用例,讲解这些裁剪属性的取值语义、适用组件、代码写法和编辑器中的交互行为,读完即可在自己的视频合成里正确、可动画、可编辑地使用裁剪。
裁剪的两种心智模型:裁掉内容,而非缩放画面
在动手写代码前,先明确裁剪语义:crop*裁剪属性描述的是「把元素某一侧的内容裁掉多少比例」,这与clipPath/object-fit的做法不同——裁剪不会重新排版、不会拉伸剩余内容,剩余画面在原布局中的位置保持不变,超出裁剪边缘的部分被直接隐藏。四个属性分别对应元素的四条边缘:cropLeft从左边缘向内裁、cropRight从右边缘向内裁、cropTop从顶部向下裁、cropBottom从底部向上裁。
取值规则:0 到 1 之间的比例
所有crop*属性的取值都是 0 到 1 之间的比例(不是像素,也不是百分比数值):
0表示该边缘不做任何裁剪;1表示该边缘完全裁剪(整条边的内容全部隐藏);- 中间值如
0.25表示裁掉该方向 25% 的内容。
例如一张1920×1080的图片设置cropLeft={0.25},实际效果就是图片左侧约 480px 被隐藏。具体的边界钳制(clamp)、异常校验以及「裁剪比例如何换算成 CSS 裁剪路径」都在源码中有明确规定,下文展开。
支持裁剪属性的组件
crop*属性并非所有组件通用,它是一组在画布合成体系下刻意设计的 props。目前支持裁剪的组件如下:
| 组件 | 来源包 | 说明 |
|---|---|---|
<Sequence> | remotion | 仅在layout="absolute-fill"时支持裁剪 |
<CanvasImage> | remotion | 画布专用图片组件 |
<Img> | remotion | 普通图片组件 |
<AnimatedImage> | remotion | 动图/图片动画组件 |
<HtmlInCanvas> | remotion | 画布内渲染 HTML 的组件 |
<Solid> | remotion | 纯色块 |
<Video> | @remotion/media | 视频组件 |
<Gif> | @remotion/gif | GIF 组件 |
<RemotionRiveCanvas> | @remotion/rive | Rive 动画画布 |
除<Sequence>与上述 Remotion 核心组件外,视频、GIF、Rive 等媒体组件也在各自的包中透传了裁剪属性,说明裁剪语义被统一抽象为「组件接收crop*后按同一套规则裁剪自身内容」。
<Sequence>的特殊限制
对<Sequence>使用crop*时,要求其layout必须是"absolute-fill"。在 Sequence.tsx 中可以看到,组件会先判断是否存在裁剪属性,再检查布局:
const cropProps = {cropLeft, cropRight, cropTop, cropBottom}; const hasCropProp = Object.values(cropProps).some( (value) => value !== undefined, ); if (layout === 'none' && hasCropProp) { throw new TypeError( 'The cropLeft, cropRight, cropTop and cropBottom props of <Sequence /> are only supported with layout="absolute-fill".', ); }也就是说,一旦给<Sequence>传入任意裁剪属性,而布局又是layout="none",会在运行期直接抛出TypeError。如果只想裁剪「某一段时长内的场景内容」,最自然的做法正是让该<Sequence>以absolute-fill铺满画面再裁剪。
裁剪的校验与数值解析:源码里的完整规则
Remotion 将裁剪逻辑集中实现在 sequence-crop.ts 中,包含三个值得了解的机制:校验(validate)、解析(resolve)、生成裁剪路径(clip path)。这些机制对所有支持crop*的组件通用。
校验规则
validateSequenceCrop对四个属性逐一校验,规则如下(sequence-crop.ts):
- 值为
undefined时跳过校验(未设置的边缘按不裁剪处理); - 值必须是有限数字(finite number),否则抛
TypeError; - 值大于
100时抛出RangeError,错误信息里特别提示:裁剪取值范围是 0 到 1,而不是 0 到 100——这是新手最容易踩的坑,误以为可以传入10代表 10%;
if (value > 100) { throw new RangeError( `The "${name}" prop of ${componentName} must be between 0 and 1, but got ${value}. The crop range is 0 to 1, not 0 to 100.`, ); }数值钳制与重叠处理
resolveSequenceCrop是数值解析的核心(sequence-crop.ts)。每个轴(水平轴的cropLeft/cropRight,垂直轴的cropTop/cropBottom)分别处理:
- 单个值会先被
clampCrop夹在[0, 1]之间:Math.min(1, Math.max(0, value ?? 0)),越界值不会崩溃而是自动收敛; - 如果同一轴上的两个值相加超过 1(例如
cropLeft={0.6}且cropRight={0.6}),意味着裁剪范围互相重叠、没有任何可见内容,Remotion 不会报错,而是把两个值都解析为0.5,即左右各保留一半重叠区间的行为被修正为「从两端各裁一半、正中间剩不下」的稳定折中:
if (resolvedStart + resolvedEnd > 1) { return [0.5, 0.5]; }裁剪如何变成 CSS:inset 裁剪路径
解析后的裁剪最终被渲染成clip-path: inset(...),规则见getSequenceCropClipPath(sequence-crop.ts):
- 四个方向都没有裁剪时返回
null(不产生任何clipPath,避免多余开销); - 否则生成形如
inset(top% right% bottom% left%)的路径,四个百分比依次为顶、右、底、左,即inset(${top * 100}% ${right * 100}% ${bottom * 100}% ${left * 100}%); - 若元素原本带
borderRadius(含borderTopLeftRadius等四个独立角),裁剪路径会追加round <radius>以保持圆角,数值半径会序列化为px。
使用裁剪的实际注入逻辑在 use-crop-style.ts 的useCropStyle中:先校验、再解析,若裁剪路径不为null,则把clipPath合并进元素已有的style,最终让单个元素同时保留原样式与裁剪结果。
基础用法与关键帧动画示例
把裁剪值看作普通的可插值数字,就能非常自然地结合interpolate()做出动态的「开合裁剪」。以下是 cropping.md 中的标准示例:一张照片在 0 到 30 帧之间从左边缘向内裁到 25%,同时固定裁掉底部 10%:
<CanvasImage src={staticFile("photo.png")} cropLeft={interpolate(frame, [0, 30], [0, 0.25], { extrapolateLeft: "clamp", extrapolateRight: "clamp", })} cropBottom={0.1} />需要注意两点:
interpolate(frame, [0, 30], [0, 0.25], ...)把帧号映射到裁剪比例,配合extrapolateLeft/extrapolateRight: "clamp"确保动画开始前与结束后都钳制在目标裁剪值上,不会越界;- 固定值(如这里的
cropBottom={0.1})与动态值可以混用,四个方向彼此独立。
对<Sequence>的裁剪方式完全一致,只是需要确保使用layout="absolute-fill":
<Sequence layout="absolute-fill" cropTop={0.2} cropBottom={0.2}> <Video src={staticFile("clip.mp4")} /> </Sequence>编辑器内可交互、可打关键帧
裁剪属性在设计上是可交互、可关键帧的。在 interactivity-schema.ts 中,四个裁剪字段被定义为统一的数字型 schema:
export const cropSchema = { cropLeft: { type: 'number', default: 0, description: 'Crop left', min: 0, max: 1, step: 0.01, hiddenFromList: false, keyframable: true, }, // cropRight / cropTop / cropBottom 结构一致 } as const satisfies InteractivitySchema;每个字段都标注了min: 0、max: 1、step: 0.01,并且keyframable: true。这意味着在画布(Canvas)中这些裁剪属性会显示为可拖动的控制点(拖动组件、裁剪边框实时更新),属性面板中以 0.01 为步进调节,并可沿时间轴打关键帧。这正是文档「优先使用crop*属性」的原因:相比手写clipPath,这套属性让裁剪在 Studio 里可直接编辑、可随帧号驱动。
这正是本 skill 文档强调的要点——结合 Interactivity Best Practices(对应仓库路径 remotion-interactivity skill)一起使用,让裁剪保持可编辑(editable)与可关键帧(keyframable),从而避免产物退化为无法在编辑器中还原的写死样式。
为什么不要手写 clipPath 与 crop 混用
文档明确给出一条约束:不要在同一元素上把clipPath与crop*属性混用。
从实现上看,useCropStyle通过把解析出的clipPath合并进 style 来生效;如果用户同时自己传入style.clipPath,二者会互相覆盖或叠加出意料之外的效果——自定义的clipPath会被裁剪路径整体替换,或与 inset 规则共同作用于元素,结果都难以预测。同时,手写clipPath无法被画布编辑器识别为裁剪操作,拖动、关键帧、时间线还原都会失效。因此只要涉及可编辑、可动画的裁剪需求,都应走crop*属性。
从测试用例验证裁剪行为
packages/core内置的测试完整验证了上述行为,可作为理解裁剪语义的权威参考:
- sequence-crop.test.tsx 验证
<Sequence cropLeft={0.1} cropRight={0.2} cropTop={0.3} cropBottom={0.4}>最终生成的clip-path恰为inset(30% 20% 40% 10%),与resolveSequenceCrop的换算完全一致; - 同一测试文件还验证了数字圆角(
borderRadius: 24→round 24px)、字符串圆角(20% / 10%)以及四个独立圆角均能在裁剪路径中被保留; - 其他覆盖裁剪的测试还包括
use-crop-style、<Img>、<HtmlInCanvas>、<Solid>与<CanvasImage>等组件的裁剪渲染,均位于 packages/core/src/test 目录下。
这些测试说明:裁剪不是某个组件各自实现的「局部 hack」,而是整套组件体系共享的、由 sequence-crop.ts 统一负责的通用能力。
裁剪使用要点速览
- 取值必须为0 到 1的比例,
0不裁,1全裁;传大于100的值会直接抛错提醒取值范围; - 四个方向相互独立,可以只传一个或任意组合;
undefined视为0; - 同一轴的裁剪相加超过
1时会被自动解析为各0.5,不会崩溃; - 动态裁剪用
interpolate(frame, ...)驱动;静态裁剪直接传数字常量; <Sequence>必须使用layout="absolute-fill",否则运行期抛TypeError;- 不要把
crop*与手写clipPath混用在同一元素上; - 使用裁剪时遵循可编辑、可关键帧原则,让裁剪结果能在画布中还原与继续调整。
在 Remotion 的画布合成工作流中,crop*系列属性提供的是「面向编辑器的声明式裁剪」,把裁剪从 CSS 细节提升为可视化、可动画、可校验的一等公民能力。无论是裁掉素材边缘、制作画幅开合动画,还是在画布上拖拽调整可见区域,从本文列出的支持组件中任选其一并传入 0 到 1 的裁剪比例即可开始。
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考