基于 Remotion + React 的竖屏短视频模板规范。
- 中文全称:字卡影·全屏叠字
- 中文简称:全屏叠字
- 英文名:CaptionReel Fullscreen Overlay(或 CaptionReel · Fullscreen Overlay)
「字卡影·全屏叠字」指竖屏影像素材全屏铺满画面,标题叠在顶部、正文叠在底部,并配合半透明文字底块的竖屏短视频。仍属 CaptionReel / 字卡影家族。
经典字卡影为「上文字 + 下 16:9 素材卡」分区布局,规范见 字卡影快闪短视频(CaptionReel)制作与排版布局规范。
由需求方提供素材并指定采用本规范;本文不规定与经典字卡影模板的选用判断。
一、视频规格
| 项目 | 规范 |
|---|---|
| 分辨率 | 1080 × 1920(9:16 竖屏) |
| 帧率 | 30 fps |
| 时长 | 8–15 秒(推荐 10 秒) |
| 编码 | H.264,CRF 18 |
| 格式 | .mp4 |
时长根据文案量和素材节奏灵活调整,不固定为 10 秒。
二、整体排版布局
2.1 核心原则
- 竖屏素材全屏铺满(
objectFit: 'cover'),不做「上字下卡」分区,也不使用 16:9 素材卡。 - 标题:绝对定位在顶部安全区,须左右居中。
- 正文:绝对定位在底部安全区。
- 标题与正文均落在半透明文字底块上(见第三节)。
┌─────────────────────────────┐ 1080
│ ↑ 上安全区(平台 UI) │
│ ┌───────────┐ │
│ │ 标题(底块)│ │ ← top 300px;标题左右居中
│ └───────────┘ │
│ │
│ (竖屏素材全屏铺满) │
│ │
│ ┌───────────────────────┐ │
│ │ 正文一句一行(底块) │ │ ← bottom padding 340px
│ └───────────────────────┘ │
│ ↓ 下安全区(文案条 / 底栏) │
└─────────────────────────────┘ 1920
← 左右各 70px →
2.2 边距与安全区(当前默认,可调)
| 项目 | 当前默认 |
|---|---|
| 顶部 padding | 300px |
| 底部 padding | 340px |
| 左右 padding | 70px |
| 内容宽度 | 1080 - 70 - 70 = 940px |
{/* 标题区 */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
padding: '300px 70px 0',
pointerEvents: 'none',
display: 'flex',
justifyContent: 'center',
}}
>
{/* 标题底块 + 标题文字 */}
</div>
{/* 正文区 */}
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
padding: '0 70px 340px',
pointerEvents: 'none',
}}
>
{/* 正文底块 + 正文行 */}
</div>
边距可随时调整,改对应 CSS 值即可。调整后须重新做 3:4 封面硬性检查(见第十一节)。
三、文字底块与可读性
3.1 底块强制
标题与正文必须使用半透明文字底块(plate)。底块是本模板可读性的主手段;描边 / 阴影仅为补充,不得用厚重全幅渐变遮罩替代底块。
3.2 底块颜色
- 颜色须从源视频画面色板推导:取场景主色后加暗、并带与场景一致的冷 / 暖色偏。
- 半透明,保证白字 / 黄高亮可读,同时仍能隐约透出底层影像。
- 禁止默认固定纯黑,除非场景本身就是近黑背景。
首版样例(明亮冷灰瓷砖场景)推导出的底块色:
const PLATE_BG = 'rgba(18, 32, 48, 0.82)';
实际制作时按素材重新取色,勿照搬此常量到所有片子。
3.3 底块样式
const plateStyle: React.CSSProperties = {
backgroundColor: PLATE_BG,
borderRadius: 14,
padding: '18px 22px',
boxShadow: '0 8px 28px rgba(0,0,0,0.28)',
};
- 标题底块:
display: 'inline-block',随标题宽度收缩,maxWidth: '100%';置于顶部安全区时由外层display: 'flex'+justifyContent: 'center'左右居中(底块本身仍为inline-block)。 - 正文底块:
width: '100%',包住全部正文行。
3.4 阴影补充
const TEXT_SHADOW =
'0 1px 2px rgba(0,0,0,0.55), 0 2px 6px rgba(0,0,0,0.35)';
标题、正文与高亮字均可叠加 textShadow,与底块并用,不单独依赖阴影保证可读。
四、字号与排版
4.1 字号规范(当前默认,可调)
| 元素 | 字号 | 字重 | 颜色 |
|---|---|---|---|
| 标题 | 60px | 800 | 纯白 #FFFFFF |
| 正文 | 40px | 500 | 近白 rgba(255,255,255,0.96) |
4.2 标题样式
标题须左右居中:外层安全区容器用 display: 'flex' + justifyContent: 'center',标题文字与底块均设 textAlign: 'center'。
<div
style={{
fontFamily: FONT_FAMILY,
fontSize: 60,
fontWeight: 800,
letterSpacing: 1,
lineHeight: 1.35,
color: '#FFFFFF',
wordBreak: 'normal',
overflowWrap: 'break-word',
textShadow: TEXT_SHADOW,
textAlign: 'center',
}}
>
示例标题:技术边界如何重新定义
</div>
4.3 正文样式
<div
style={{
fontFamily: FONT_FAMILY,
fontSize: 40,
fontWeight: 500,
lineHeight: 1.5,
color: 'rgba(255,255,255,0.96)',
}}
>
{/* 每行一句,见第六节 */}
</div>
正文行建议:
const bodyLineStyle: React.CSSProperties = {
wordBreak: 'normal',
overflowWrap: 'break-word',
letterSpacing: 1,
transformOrigin: 'left center',
marginBottom: 18,
textShadow: TEXT_SHADOW,
};
4.4 关键词高亮
统一用黄色加粗高亮:
const HIGHLIGHT = '#F1C40F';
const Hi: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<span
style={{
color: HIGHLIGHT,
fontWeight: 800,
textShadow: TEXT_SHADOW,
}}
>
{children}
</span>
);
// 使用
<Hi>关键词</Hi>
4.5 换行与断行
- 使用浏览器自然换行:
wordBreak: 'normal',overflowWrap: 'break-word'。 - 禁止人工插入强制换行去「凑行」;靠改写文案解决孤字、断行难看等问题。
- 行宽应尽量铺满内容区,避免标点独占一行或句末只剩单字。
五、动画节奏
5.1 节奏约定
与经典字卡影一致:
- 所有文字在 1 秒(30 帧)内全部入场完成。
- 标题先出现(
start = 0)。 - 正文每行错开 4 帧依次淡入。
- 每一项淡入持续 7 帧,并带轻微缩放(0.95 → 1.0)。
- 只保留淡入 + 轻微缩放,不加闪屏、不加白闪、不加其他特效。
- 本模板不使用分隔线;emoji 非必须。
5.2 动画代码
const frame = useCurrentFrame();
const appear = (start: number): number =>
interpolate(frame, [start, start + 7], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const titleOpacity = appear(0);
const titleScale = 0.95 + 0.05 * titleOpacity;
const lineAnim = (i: number): React.CSSProperties => {
const progress = appear(4 * (i + 1));
return {
opacity: progress,
transform: `scale(${0.95 + 0.05 * progress})`,
};
};
标题 transformOrigin 用 'center top'(与左右居中一致);正文行用 'left center'。
六、文案规范
本节为硬性约定,成片文案须遵守。
- 语体:正式书面表达,不用口语、俚语或网络梗堆砌。
- 正文结构:一句一行,不写成一整段。
- 内容重心:写宏观意义或可引发讨论的判断;不要逐镜头复述画面发生了什么。
- 断行:不得出现换行后单独一个字落在行末 / 行首;靠改写解决,不靠手动强制断行。
- 自然换行:依赖浏览器
wordBreak: 'normal';行应尽量填满内容宽度;不要为标点单独断行。 - 画面内文字范围:视频叠字只含标题 + 正文;来源署名、出处说明不写进画面文字。
- 关键词:用
Hi组件黄色#F1C40F+ 加粗标出。
七、素材
7.1 素材要求
- 推荐竖屏或接近 9:16 的影像,能铺满 1080×1920。
- 使用
OffthreadVideo,muted,objectFit: 'cover',objectPosition: 'center center'。 - 本模板不使用 16:9 素材卡或上下黑边信箱构图;若素材过宽,由
cover裁切,并保证主体仍在画面中。
7.2 调用方式
import { OffthreadVideo, staticFile } from 'remotion';
<OffthreadVideo
src={staticFile('your-video.mp4')}
muted
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'center center',
}}
/>
八、完整组件代码示例
以下为消毒后的 FlashVideo 示例:采用本节默认边距与字号,文案为占位正式书面语。底块色需按实际素材重取。
import React from 'react';
import {
AbsoluteFill,
OffthreadVideo,
staticFile,
interpolate,
useCurrentFrame,
} from 'remotion';
const FONT_FAMILY =
'"Noto Sans CJK SC", "Noto Sans SC", "PingFang SC", sans-serif';
const HIGHLIGHT = '#F1C40F';
/** 须按源视频色板重取;以下仅为明亮冷灰场景的推导示例 */
const PLATE_BG = 'rgba(18, 32, 48, 0.82)';
const TEXT_SHADOW =
'0 1px 2px rgba(0,0,0,0.55), 0 2px 6px rgba(0,0,0,0.35)';
const Hi: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<span
style={{
color: HIGHLIGHT,
fontWeight: 800,
textShadow: TEXT_SHADOW,
}}
>
{children}
</span>
);
const plateStyle: React.CSSProperties = {
backgroundColor: PLATE_BG,
borderRadius: 14,
padding: '18px 22px',
boxShadow: '0 8px 28px rgba(0,0,0,0.28)',
};
export const FlashVideo: React.FC = () => {
const frame = useCurrentFrame();
const appear = (start: number): number =>
interpolate(frame, [start, start + 7], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const titleOpacity = appear(0);
const titleScale = 0.95 + 0.05 * titleOpacity;
const lineAnim = (i: number): React.CSSProperties => {
const progress = appear(4 * (i + 1));
return {
opacity: progress,
transform: `scale(${0.95 + 0.05 * progress})`,
};
};
const bodyLineStyle: React.CSSProperties = {
wordBreak: 'normal',
overflowWrap: 'break-word',
letterSpacing: 1,
transformOrigin: 'left center',
marginBottom: 18,
textShadow: TEXT_SHADOW,
};
return (
<AbsoluteFill style={{ backgroundColor: '#000000' }}>
<AbsoluteFill>
<OffthreadVideo
src={staticFile('highlights.mp4')}
muted
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'center center',
}}
/>
</AbsoluteFill>
{/* 标题 — 顶区 + 底块(左右居中) */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
padding: '300px 70px 0',
pointerEvents: 'none',
display: 'flex',
justifyContent: 'center',
}}
>
<div
style={{
...plateStyle,
opacity: titleOpacity,
transform: `scale(${titleScale})`,
transformOrigin: 'center top',
display: 'inline-block',
maxWidth: '100%',
boxSizing: 'border-box',
textAlign: 'center',
}}
>
<div
style={{
fontFamily: FONT_FAMILY,
fontSize: 60,
fontWeight: 800,
letterSpacing: 1,
lineHeight: 1.35,
color: '#FFFFFF',
wordBreak: 'normal',
overflowWrap: 'break-word',
textShadow: TEXT_SHADOW,
textAlign: 'center',
}}
>
技术边界如何重新定义日常体验
</div>
</div>
</div>
{/* 正文 — 底区 + 底块 */}
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
padding: '0 70px 340px',
pointerEvents: 'none',
}}
>
<div style={{ ...plateStyle, width: '100%', boxSizing: 'border-box' }}>
<div
style={{
fontFamily: FONT_FAMILY,
fontSize: 40,
fontWeight: 500,
lineHeight: 1.5,
color: 'rgba(255,255,255,0.96)',
}}
>
<div style={{ ...lineAnim(0), ...bodyLineStyle }}>
外形看似轻巧,内核却是<Hi>精密的控制与反馈</Hi>。
</div>
<div style={{ ...lineAnim(1), ...bodyLineStyle }}>
当交互从工具走向陪伴,边界开始松动。
</div>
<div style={{ ...lineAnim(2), ...bodyLineStyle, marginBottom: 0 }}>
下一步,是功能升级,还是<Hi>关系重构</Hi>?
</div>
</div>
</div>
</div>
</AbsoluteFill>
);
};
九、工程目录与渲染
9.1 关键文件
/workspace/captionreel-fullscreen/ # 示例工程名,可自定
├── src/
│ ├── index.ts # Remotion 入口
│ ├── Root.tsx # 根组件
│ └── FlashVideo.tsx # 全屏叠字视频组件(核心)
├── public/
│ └── *.mp4 # 竖屏 / 近 9:16 视频素材
├── package.json
├── tsconfig.json
└── remotion.config.ts
9.2 渲染命令
cd /workspace/captionreel-fullscreen
# 渲染最终视频
npx remotion render src/index.ts FlashVideo out/video.mp4 --codec=h264 --crf=18
# 渲染单帧预览(建议选文字已全部入场后的帧)
npx remotion still src/index.ts FlashVideo out/preview.png --frame=60
十、快速修改对照表
| 想改什么 | 改哪里 | 当前默认 |
|---|---|---|
| 顶部边距 | 标题容器 padding 上边 | 300px |
| 底部边距 | 正文容器 padding 下边 | 340px |
| 左右边距 | 标题 / 正文容器 padding 左右 | 70px |
| 标题字号 | 标题 fontSize | 60 |
| 正文字号 | 正文 fontSize | 40 |
| 底块颜色 | PLATE_BG | 按素材色板重取 |
| 高亮颜色 | HIGHLIGHT | #F1C40F |
| 入场节奏 | appear 函数 | [start, start + 7];正文错开 4 帧 |
| 视频素材 | OffthreadVideo 的 src | staticFile('xxx.mp4') |
| 视频时长 | Root / Composition 的 durationInFrames | 300(10 秒 @30fps) |
| 封面 / 首帧 | 成片中选帧序号 N(须文字全部入场) | 裁 crop=1080:1440:0:240;无音轨跳过 adelay |
十一、封面与首帧
封面从成片中取一帧,不另做设计。流程对齐 HeroClip 封面与首帧,并按全屏叠字特点做以下约定。
- 选帧:必须在标题与正文全部入场完成之后再选(入场约 1 秒内结束,30fps 下通常
N ≥ 30;若正文行数更多,取最后一行淡入结束后的帧)。在此前提上,优先挑主体最清楚、最有代表性的一帧。不要选文字尚未齐的前几帧。 - 比例:平台要求 3:4。以画面中心为原点,上下等量裁切:1080×1920 → 1080×1440,上下各裁 240px(保留区 y = 240–1680)。ffmpeg:
crop=1080:1440:0:240。 - 硬性检查(本模板,无独立素材卡):裁切后的
cover_3x4.png上,标题底块 + 正文底块必须完整落在 y = 240–1680 内,且裁切后主体仍清晰可见。若底块被吃掉或主体被裁没,须先改布局(边距 / 字号 / 底块内边距)再出封面,不得带病上线。 - 首帧:把选中帧的完整 9:16 画面插到视频最前面,只占 1 帧,用于平台自动取首帧作默认封面。平台按 3:4 居中裁切后,结果就是封面图。成片总帧数因此多出 1 帧。
11.1 无音轨(默认)
全屏叠字多数成片无音轨。无音轨时跳过 adelay,只做取帧、裁封面、插首帧。
IN=render.mp4 # Remotion 成片(通常无音轨)
FPS=30 # = 成片帧率(规范默认 30)
N=90 # 选中帧序号 = 秒数 × FPS;须 ≥ 文字全部入场完成
ffmpeg -i $IN -vf "select=eq(n\,$N)" -frames:v 1 first_frame.png # 9:16 完整帧
ffmpeg -i first_frame.png -vf "crop=1080:1440:0:240" cover_3x4.png # 3:4 封面
# 硬性检查:目视 cover_3x4.png
# 1) 标题底块 + 正文底块完整可见(落在 y=240–1680)
# 2) 主体清晰可见
ffmpeg -loop 1 -framerate $FPS -i first_frame.png -i $IN \
-filter_complex "\
[0:v]scale=out_range=tv,format=yuv420p,setsar=1,fps=$FPS,trim=end_frame=1,setpts=PTS-STARTPTS[f];\
[1:v]scale=in_range=pc:out_range=tv,format=yuv420p,setsar=1,fps=$FPS,setpts=PTS-STARTPTS[m];\
[f][m]concat=n=2:v=1:a=0,setpts=N/($FPS*TB)[v]" \
-map "[v]" -r $FPS \
-c:v libx264 -crf 16 -pix_fmt yuv420p -color_range tv \
-an -movflags +faststart final.mp4
- 复核:
ffprobe -count_frames帧数 = 原帧数 + 1;- 成片仍无音轨(
-an); - 首帧居中裁 3:4 后与
cover_3x4.png一致;第 2 帧与原片第 1 帧一致; cover_3x4.png上标题底块、正文底块完整可见,且主体清晰(硬性)。
11.2 有音轨(例外)
若某条成片带原声,插帧后须把音频整体后延 1 帧,否则音画错位。命令与像素格式处理见 HeroClip · 封面与首帧(adelay 按采样数延迟、yuv420p TV 范围拼接)。本模板选帧与 3:4 硬性检查规则仍以本节为准。
十二、注意事项
- 不要用
<Loop>包裹OffthreadVideo,可能导致叠字与素材时序异常。需要循环的素材请先用 ffmpeg 预处理,或直接准备足够时长的素材。 - 文字底块强制:颜色从素材色板推导并半透明;禁止无理由默认纯黑。
- 本模板无 16:9 素材卡、无分隔线;不要把经典字卡影的分区布局抄进来。
- 文字入场只保留淡入 + 轻微缩放,避免过多动效干扰阅读。
- 文案遵守第六节:正式书面、一句一行、宏观意义、无画面内出处署名。
- 改边距或字号后,必须重新做第十一节的 3:4 封面硬性检查。
- 封面:选帧时标题与正文须已全部入场;3:4 裁切后标题底块、正文底块完整可见,且主体清晰(硬性检查)。
相关规范:字卡影快闪短视频(CaptionReel)制作与排版布局规范(经典「上文字 + 16:9 素材卡」)。
本文档作为 CaptionReel 字卡影·全屏叠字模板的设计与开发规范,持续迭代中。