跳到正文
返回

贴文卡(PostCard)短视频制作规范

基于 Remotion + React 的竖屏短视频模板规范。

  • 中文名:贴文卡
  • 英文名:PostCard

「贴文卡」指模拟社交媒体帖文截图,将柔白色卡片居中放置在纯黑动态渐变背景上的竖屏短视频


一、视频规格

项目规范
分辨率1080 × 1920(9:16 竖屏)
帧率30 fps
时长10 秒(300 帧)
编码H.264,CRF 18
格式.mp4

二、画面结构

2.1 整体布局

┌─────────────────────────────┐
│  [头像]  昵称                │
│          YYYY-MM-DD         │
│                             │
│  正文第 1 段(整段渲染,     │
│  浏览器按宽度自动换行)      │
│  ...                        │
│  正文第 N 段                 │
│                             │
│                    HH:MM    │
│                    星期X     │
└─────────────────────────────┘

2.2 默认账号信息

元素默认值
头像https://ai-daily.tech/brand/logo.png
昵称高老师的分享局
头部日期动态取当前日期,格式 YYYY-MM-DD
底部时间戳动态取当前时间,格式 HH:MM + 换行 + 星期X

三、画布与背景

3.1 画布规格

属性
宽度1080px
高度1920px
宽高比9:16(竖屏)
帧率30fps
时长10 秒(300 帧)

3.2 背景样式


四、卡片规范

4.1 卡片外观

属性
背景色#F5F3F0(柔白)
宽度880px(固定)
高度由内容自动撑开
圆角36px
内边距50px
投影0 8px 40px rgba(0,0,0,0.35)
定位水平垂直居中(flexbox)

4.2 头部(头像 + 昵称 + 日期)

属性
头像尺寸80×80px
头像形状圆形(borderRadius: 50%
昵称字号40px
昵称字重700
昵称颜色#1A1A1A
日期字号28px
日期颜色#999999
布局头像在左,昵称+日期在右,垂直居中
与正文间距44px

4.3 正文

属性
字号46px
字重700
颜色#1A1A1A
行距1.5
行间距6px
对齐左对齐
高亮词颜色#F5A074(橙色)
高亮词字重800

正文内容由调用方传入,支持纯文本和高亮词混合。高亮词用 <Hi> 组件包裹。

正文排版(硬性要求):

4.4 底部时间戳

属性
字号24px
颜色#999999
对齐右对齐
格式HH:MM + 换行 + 星期X
位置卡片底部右下角,自动贴底

五、动画规范

时间线(帧):

元素开始帧结束帧
头部(头像+昵称+日期)09
正文第 1 段615
正文第 2 段1120
正文第 3 段1625
正文第 N 段6 + (N-1)*515 + (N-1)*5
底部时间戳5564

所有文字在第 60 帧(2 秒)内全部展示完毕。


六、色彩规范

用途色值说明
背景底色#000000纯黑
渐变光效rgba(245,160,116, 0.15~0.25)橙色半透明
卡片背景#F5F3F0柔白,不刺眼
正文颜色#1A1A1A近黑
高亮词#F5A074橙色,与背景光效呼应
辅助文字#999999灰色(日期、时间戳)

七、字体

font-family: "Noto Sans CJK SC", "PingFang SC", "Microsoft YaHei", sans-serif;

八、从零搭建步骤

8.1 创建项目目录

mkdir postcard && cd postcard

8.2 创建 package.json

{
  "name": "postcard",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "studio": "remotion studio",
    "render": "remotion render PostCard out/video.mp4"
  },
  "dependencies": {
    "@remotion/cli": "4.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "remotion": "4.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.0.0",
    "typescript": "^5.0.0"
  }
}

8.3 创建 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["src"]
}

8.4 创建 src/index.ts

import { registerRoot } from 'remotion';
import { RemotionRoot } from './Root';

registerRoot(RemotionRoot);

8.5 创建 src/Root.tsx

import React from 'react';
import { Composition } from 'remotion';
import { PostCard } from './PostCard';

export const RemotionRoot: React.FC = () => {
  return (
    <>
      <Composition
        id="PostCard"
        component={PostCard}
        durationInFrames={300}
        fps={30}
        width={1080}
        height={1920}
      />
    </>
  );
};

8.6 创建 src/PostCard.tsx

import React from 'react';
import {
  AbsoluteFill,
  Img,
  interpolate,
  useCurrentFrame,
} from 'remotion';

const FONT = '"Noto Sans CJK SC", "PingFang SC", "Microsoft YaHei", sans-serif';
const CARD = '#F5F3F0';
const TEXT = '#1A1A1A';
const ORANGE = '#F5A074';
const GRAY = '#999999';

const NICKNAME = '高老师的分享局';

const Hi: React.FC<{ children: React.ReactNode }> = ({ children }) => (
  <span style={{ color: ORANGE, fontWeight: 800 }}>{children}</span>
);

const now = new Date();
const DATE_STR = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
const TIME_STR = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
const WEEKDAYS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const WEEKDAY = WEEKDAYS[now.getDay()];

// 正文按自然段落组织,每个元素是一段;段内换行交给浏览器自然排版,禁止手动分行
const paragraphs: React.ReactNode[] = [
  <>在经过了长期的思想斗争以后,现在正式不用 IDE 了,<Hi>Cursor</Hi> 也正式切换成了 <Hi>Agent View</Hi><Hi>VSCode</Hi> 也很少打开了。</>,
  <>代码只关注 <Hi>架构</Hi>,每次简单浏览 <Hi>Diff</Hi><Hi>Code Review</Hi> 交给多个 Agent 互相 review,验证就是完备的 E2E 了。</>,
  <>未来代码就是黑盒了,这是心酸但是没办法的事实了。</>,
];

export const PostCard: React.FC = () => {
  const frame = useCurrentFrame();

  const fade = (start: number, dur = 9) =>
    interpolate(frame, [start, start + dur], [0, 1], {
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp',
    });

  return (
    <AbsoluteFill style={{ backgroundColor: '#000000' }}>
      {[0, 1, 2].map((i) => {
        const s = 0.15 + i * 0.08;
        const cx = 50 + Math.sin(frame * s * 0.03 + i * 2.1) * 25;
        const cy = 50 + Math.cos(frame * s * 0.025 + i * 1.7) * 30;
        return (
          <div
            key={i}
            style={{
              position: 'absolute',
              inset: 0,
              background: `radial-gradient(circle ${800 + i * 150}px at ${cx}% ${cy}%, rgba(245,160,116,${0.15 + i * 0.05}), transparent 70%)`,
            }}
          />
        );
      })}

      <div style={{ position: 'absolute', inset: 0, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
        <div style={{ width: 880, backgroundColor: CARD, borderRadius: 36, padding: 50, display: 'flex', flexDirection: 'column', boxShadow: '0 8px 40px rgba(0,0,0,0.35)' }}>
          <div style={{ display: 'flex', alignItems: 'center', marginBottom: 44, opacity: fade(0) }}>
            <Img src="https://ai-daily.tech/brand/logo.png" style={{ width: 80, height: 80, borderRadius: '50%', objectFit: 'cover' }} />
            <div style={{ marginLeft: 18 }}>
              <div style={{ fontFamily: FONT, fontSize: 40, fontWeight: 700, color: TEXT, lineHeight: 1.3 }}>{NICKNAME}</div>
              <div style={{ fontFamily: FONT, fontSize: 28, color: GRAY, lineHeight: 1.4 }}>{DATE_STR}</div>
            </div>
          </div>

          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
            {paragraphs.map((p, i) => (
              <p key={i} style={{ opacity: fade(6 + i * 5), fontFamily: FONT, fontSize: 46, fontWeight: 700, color: TEXT, lineHeight: 1.6, margin: '0 0 6px', transformOrigin: 'left center' }}>
                {p}
              </p>
            ))}
          </div>

          <div style={{ fontFamily: FONT, fontSize: 24, color: GRAY, marginTop: 'auto', paddingTop: 16, opacity: fade(55), textAlign: 'right' }}>
            {TIME_STR}<br />{WEEKDAY}
          </div>
        </div>
      </div>
    </AbsoluteFill>
  );
};

8.7 安装依赖并渲染

npm install
npx remotion render PostCard out/video.mp4

Remotion 会自动检测系统中的浏览器。如需指定,添加 --browser-executable=/path/to/chrome

输出文件:out/video.mp4


九、定制说明

制作新视频时,只需修改 src/PostCard.tsx 中的 paragraphs 数组,把新的正文按自然段落传入(一个元素对应一段,段尾有完整句号)。高亮词用 <Hi> 包裹。段内换行由浏览器按卡片宽度自然排版,不要手动分行、不要设置每行字数

const paragraphs: React.ReactNode[] = [
  <>第 1 段普通文字,浏览器自动折行。</>,
  <>第 2 段带 <Hi>高亮词</Hi> 的文字,同样整体渲染。</>,
  // ...
];

十、项目结构

postcard/
├── src/
│   ├── index.ts               # Remotion 入口
│   ├── Root.tsx               # 根组件
│   └── PostCard.tsx           # 贴文卡视频组件(核心)
├── package.json
├── tsconfig.json
└── out/
    └── video.mp4              # 渲染输出

本文档作为 PostCard 贴文卡短视频模板的设计与开发规范,持续迭代中。


上一篇
「高老师的分享局」博客发布流程与规范
下一篇
AI 原生软件开发生命周期实战手册(中文翻译)