跳到正文
返回

字卡影·全屏叠字(CaptionReel Fullscreen Overlay)制作与排版布局规范

基于 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 核心原则

┌─────────────────────────────┐  1080
│  ↑ 上安全区(平台 UI)        │
│       ┌───────────┐         │
│       │ 标题(底块)│         │  ← top 300px;标题左右居中
│       └───────────┘         │
│                             │
│     (竖屏素材全屏铺满)      │
│                             │
│  ┌───────────────────────┐  │
│  │ 正文一句一行(底块)    │  │  ← bottom padding 340px
│  └───────────────────────┘  │
│  ↓ 下安全区(文案条 / 底栏)  │
└─────────────────────────────┘  1920
         ← 左右各 70px →

2.2 边距与安全区(当前默认,可调)

项目当前默认
顶部 padding300px
底部 padding340px
左右 padding70px
内容宽度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)',
};

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 字号规范(当前默认,可调)

元素字号字重颜色
标题60px800纯白 #FFFFFF
正文40px500近白 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 换行与断行


五、动画节奏

5.1 节奏约定

与经典字卡影一致:

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'。


六、文案规范

本节为硬性约定,成片文案须遵守。

  1. 语体:正式书面表达,不用口语、俚语或网络梗堆砌。
  2. 正文结构:一句一行,不写成一整段。
  3. 内容重心:写宏观意义或可引发讨论的判断;不要逐镜头复述画面发生了什么。
  4. 断行:不得出现换行后单独一个字落在行末 / 行首;靠改写解决,不靠手动强制断行。
  5. 自然换行:依赖浏览器 wordBreak: 'normal';行应尽量填满内容宽度;不要为标点单独断行。
  6. 画面内文字范围:视频叠字只含标题 + 正文;来源署名、出处说明不写进画面文字。
  7. 关键词:用 Hi 组件黄色 #F1C40F + 加粗标出。

七、素材

7.1 素材要求

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
标题字号标题 fontSize60
正文字号正文 fontSize40
底块颜色PLATE_BG按素材色板重取
高亮颜色HIGHLIGHT#F1C40F
入场节奏appear 函数[start, start + 7];正文错开 4 帧
视频素材OffthreadVideo 的 srcstaticFile('xxx.mp4')
视频时长Root / Composition 的 durationInFrames300(10 秒 @30fps)
封面 / 首帧成片中选帧序号 N(须文字全部入场)裁 crop=1080:1440:0:240;无音轨跳过 adelay

十一、封面与首帧

封面从成片中取一帧,不另做设计。流程对齐 HeroClip 封面与首帧,并按全屏叠字特点做以下约定。

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

11.2 有音轨(例外)

若某条成片带原声,插帧后须把音频整体后延 1 帧,否则音画错位。命令与像素格式处理见 HeroClip · 封面与首帧(adelay 按采样数延迟、yuv420p TV 范围拼接)。本模板选帧与 3:4 硬性检查规则仍以本节为准。


十二、注意事项

  1. 不要用 <Loop> 包裹 OffthreadVideo,可能导致叠字与素材时序异常。需要循环的素材请先用 ffmpeg 预处理,或直接准备足够时长的素材。
  2. 文字底块强制:颜色从素材色板推导并半透明;禁止无理由默认纯黑。
  3. 本模板无 16:9 素材卡、无分隔线;不要把经典字卡影的分区布局抄进来。
  4. 文字入场只保留淡入 + 轻微缩放,避免过多动效干扰阅读。
  5. 文案遵守第六节:正式书面、一句一行、宏观意义、无画面内出处署名。
  6. 改边距或字号后,必须重新做第十一节的 3:4 封面硬性检查。
  7. 封面:选帧时标题与正文须已全部入场;3:4 裁切后标题底块、正文底块完整可见,且主体清晰(硬性检查)。

相关规范:字卡影快闪短视频(CaptionReel)制作与排版布局规范(经典「上文字 + 16:9 素材卡」)。

本文档作为 CaptionReel 字卡影·全屏叠字模板的设计与开发规范,持续迭代中。


上一篇
像素 MV 顶格迭代 Skill:从优秀案例拆配方到可复用工作流
下一篇
一个 Skill把任意机械图变成可拆解的 3D 交互网页