基于 HyperFrames(单 HTML + GSAP 单条 paused 时间轴)的快讯风竖屏短视频模板规范。
- 中文名:快讯风竖屏短视频
- 英文名:NewsFlash
用标题、副标题、视觉素材卡、正文分段四层结构呈现单条快讯:无旁白、无 BGM,靠文字级联入场、素材播放和待机氛围动效撑住节奏。
一、视频规格
| 项目 | 规范 |
|---|---|
| 分辨率 | 1080 × 1920(9:16 竖屏) |
| 时长 | = 处理后素材片段的精确时长(ffprobe 实测),不固定区间、不额外加停留 |
| 帧率 | 让时长为整数帧、源帧均匀映射(源 24fps × 1.5 → 36fps;否则 30 或 60fps) |
| 格式 | .mp4(无音轨) |
| 技术栈 | HyperFrames:单 index.html + GSAP 单条 paused 时间轴 |
#root 的 data-duration 与 data-fps 必须等于编码后素材片段的时长与帧率。
二、整体排版布局
- 自上而下:标题 → 副标题 → 素材卡 → 正文分段。
- 四层作为一个整体在画面中上下居中(flex 纵向 +
justify-content: center),剩余空间即安全区。 - 内容区宽 880px,左右边距各 100px。
- 标题、副标题居中;正文左对齐。
<div id="root" data-composition-id="main"
data-width="1080" data-height="1920"
data-duration="20" data-fps="36">
<!-- 背景装饰层 z-index: 0 -->
<!-- .frame 内容列 z-index: 1 -->
<!-- .vignette 暗角层 z-index: 2 -->
</div>
#root { position: relative; width: 1080px; height: 1920px; overflow: hidden; background: var(--bg); }
.frame {
position: relative; z-index: 1;
width: var(--content-w); /* 880px */
height: 100%; margin: 0 auto;
display: flex; flex-direction: column; justify-content: center;
text-align: center;
}
间距
| 项目 | 值 |
|---|---|
| 标题 → 副标题 | 12px |
| 副标题 → 素材卡 | 22px |
| 素材卡 → 正文 | 24px |
| 正文行高 | 1.45 |
| 正文段间距 | 13px |
出片前必须 snapshot 确认四层总高在 1920px 内;超高时先收紧间距,再按 2px 步进缩小正文字号。
三、配色与字体
3.1 配色(浅色纸感)
| 变量 | 值 | 用途 |
|---|---|---|
--bg | #f7f3ea | 暖白纸感底色 |
--fg | #202634 | 标题主色 |
--fg-dim | #5c6470 | 副标题 |
--accent | #8f5e00 | token 强调、正文高亮(浅底上满足 WCAG AA) |
| 正文色 | #2c3240 | 正文 |
--card-border | rgba(143, 94, 0, 0.28) | 素材卡描边 |
强调色取深色变体并通过对比度检查,不直接用素材亮色。
3.2 背景装饰
/* 柔焦暖色斑 ×2:右上金、左下橙 */
#blob-a { top: -280px; right: -260px;
background: radial-gradient(circle, rgba(240, 205, 145, 0.55), transparent 68%); }
#blob-b { bottom: -300px; left: -280px;
background: radial-gradient(circle, rgba(233, 178, 132, 0.4), transparent 68%); }
/* 两侧斜向金色细线,氛围循环中做透明度呼吸 */
#edge-a { top: 150px; left: -70px; transform: rotate(-33deg);
background: linear-gradient(90deg, transparent, rgba(143, 107, 42, 0.85)); opacity: 0.28; }
#edge-b { bottom: 220px; right: -80px; transform: rotate(28deg);
background: linear-gradient(90deg, transparent, rgba(143, 107, 42, 0.7)); opacity: 0.20; }
/* 极浅暖色暗角 */
.vignette { background: radial-gradient(ellipse at 50% 46%, transparent 58%, rgba(120, 100, 60, 0.18) 100%); }
- 色斑、细线为出血元素,加
data-layout-allow-overflow;正文与素材卡不允许出血。 - 暗角层
z-index: 2且pointer-events: none。
3.3 字体:苹方内嵌 woff2
@font-face { font-family: "PingFang SC"; font-weight: 100 450;
src: url("assets/fonts/PingFangSC-Regular.woff2") format("woff2"); }
@font-face { font-family: "PingFang SC"; font-weight: 451 550;
src: url("assets/fonts/PingFangSC-Medium.woff2") format("woff2"); }
@font-face { font-family: "PingFang SC"; font-weight: 551 700;
src: url("assets/fonts/PingFangSC-Semibold.woff2") format("woff2"); }
#root { font-family: "PingFang SC", sans-serif; }
- 用 fontTools 从
PingFang.ttc取 SC 字族,按本片文案用字做子集,生成 Regular / Medium / Semibold 三个 woff2 放入assets/fonts/。 - 文案改动后重新子集,确认三档不缺字;禁止依赖系统字体。
四、文字层
4.1 字号与字重
| 元素 | 字号 | 字重 | 颜色 |
|---|---|---|---|
| 标题 | 62px,必须单行 | 600 | --fg,强调 token 用 --accent |
| 副标题 | 43px | 400 | --fg-dim |
| 正文 | 38px | 500 | #2c3240,关键词 --accent |
标题放不下一行时,用负 letter-spacing 和/或降字号(不低于约 54px)。
4.2 标题 token 化
标题按词拆成 .tk(inline-block,初始 opacity 0)供级联入场,1–2 个核心词加 .tk-ac:
<h1 id="title"
><span class="tk" id="tk1">Claude </span
><span class="tk tk-ac" id="tk2">4 行</span
><span class="tk" id="tk3">提示词,</span
><span class="tk" id="tk4">画出</span
><span class="tk tk-ac" id="tk5">人的一生</span></h1
>
#title { font-size: 62px; font-weight: 600; line-height: 1.26; color: var(--fg); }
.tk { display: inline-block; opacity: 0; will-change: transform; }
.tk-ac { color: var(--accent); }
4.3 副标题
一行完整句,概括看点,不与正文重复:
#subtitle {
margin: 12px 0 0;
font-size: 43px;
font-weight: 400;
line-height: 1.4;
color: var(--fg-dim);
opacity: 0;
}
4.4 正文
<div id="body-paras">
<p>这段 30 秒动画<span class="hl">《人的一生》</span>,从一个细胞开始,到出生、长大、再孕育下一代,每一帧都是 Claude 用 JS 画的。</p>
<p>一共写了两段提示词,加起来 <span class="hl">4 行</span>,思考力度拉满。</p>
<p>整个过程跑了 2 小时,用掉 <span class="hl">2700 万 token</span>。</p>
</div>
#body-paras { margin: 0; text-align: left; width: 100%; }
#body-paras p {
margin: 0;
font-size: 38px;
font-weight: 500;
line-height: 1.45;
color: #2c3240;
opacity: 0;
will-change: transform;
}
#body-paras p + p { margin-top: 13px; }
.hl { font-style: normal; color: var(--accent); }
文案来源
- 标题、副标题、正文由我们根据来源帖撰写(翻译 / 概括,口语化),经用户审核通过后再制作。
- 观点注明人名(如「作者认为」「某某表示」),不用「他说」式表述。
- 正文段数、行数不固定,按内容安排;每段段尾标点完整。
- 每段高亮 0–1 处,全片高亮收敛在关键数字与结论,结尾金句重点高亮。
换行
- 浏览器在 880px 内自然折行;不用
white-space: nowrap,不手动<br>。 - 不允许单字成行(孤字)或半截短行:优先增删几个字调整文案,其次对单段微调
letter-spacing(约 ±1.5px)。 - 标题保持单行。
五、素材层
5.1 素材处理(全部在 ffmpeg 完成)
- 统一
setpts=PTS/<倍速>、去音轨(-an)、CFR、每 0.25s 一个关键帧。 - 不用
playbackRate,不做反向播放或往返循环。 - 时长按视频流计算(音轨可能更长)。
(a) 高光拼接:按文案剪 2–4 段,拼成约 15s,再 1.5× → 约 10s。
ffmpeg -i source.mp4 -filter_complex \
"[0:v]trim=2:7,setpts=PTS-STARTPTS[a];[0:v]trim=12:17,setpts=PTS-STARTPTS[b];[0:v]trim=24:29,setpts=PTS-STARTPTS[c];\
[a][b][c]concat=n=3:v=1:a=0,setpts=PTS/1.5,fps=36[v]" \
-map "[v]" -an -fps_mode cfr -c:v libx264 -crf 18 -pix_fmt yuv420p \
-force_key_frames "expr:gte(t,n_forced*0.25)" -sc_threshold 0 assets/clip.mp4
(b) 整段原片:完整源片,按用户指定倍速(1× / 1.5× / 2×),不剪辑。
ffmpeg -i source.mp4 -vf "setpts=PTS/1.5,fps=36" \
-an -fps_mode cfr -c:v libx264 -crf 18 -pix_fmt yuv420p \
-force_key_frames "expr:gte(t,n_forced*0.25)" -sc_threshold 0 assets/clip.mp4
量时长与帧数(结果写入 data-duration / data-fps):
ffprobe -v error -select_streams v:0 -count_frames \
-show_entries stream=duration,nb_read_frames,r_frame_rate -of default=nw=1 assets/clip.mp4
5.2 素材卡
- 宽 880px;
aspect-ratio= 源片真实比例(如1920 / 1080),高度从第 0 帧锁死。 object-fit: contain,不裁切。- 圆角 20px,1.5px 描边,卡底
#fdfbf5。
.media-wrap { position: relative; margin: 22px 0 24px; width: 100%; opacity: 0; }
.card-glow {
position: absolute; inset: -34px;
background: radial-gradient(closest-side, rgba(230, 170, 80, 0.5), transparent 72%);
opacity: 0.26; pointer-events: none;
}
.card-float { position: relative; z-index: 1; width: 100%; }
.media-card {
overflow: hidden;
border-radius: 20px;
border: 1.5px solid var(--card-border);
background: #fdfbf5;
}
.media-card video {
display: block;
width: 100%;
aspect-ratio: 1920 / 1080; /* = 源片比例 */
object-fit: contain;
}
<video id="clip" src="assets/clip.mp4"
data-start="0" data-duration="20"
muted playsinline></video>
data-start="0",data-duration= 素材片段时长;data-start/data-duration只写在 video 元素上。muted+playsinline,不带crossorigin。
六、动效
6.1 原则
- GSAP 单条 paused 时间轴,挂到
window.__timelines["main"]。 - 文字只做「淡入 + 轻位移」,出现后保留,不淡出;节奏靠
power4.out。 - 素材卡第 0 帧即完全可见(autoAlpha 1),入场只有 scale 0.96 → 1 回弹,无淡入、无 x/y 位移。
- 待机氛围只用
sin纯时间函数,禁止随机数与真实时钟。
6.2 时间轴常量与节拍
var F = 1 / 36; // 单帧时长 = 1 / data-fps
var T_CARD = 0; // 素材卡
var T_TITLE = 0.1; // 标题级联起点
var T_SUBTITLE = 0.62; // 副标题
var T_BODY = 1.0; // 正文首段
var BODY_GAP = 0.28; // 正文段间隔
var T_AMBIENT = 0.5; // 待机氛围起点
var AMBIENT_PERIOD = 3.2; // 氛围呼吸周期
var COMP_DURATION = 20; // = data-duration
| 节拍 | 时刻 | 动效 |
|---|---|---|
| 素材卡 | 0s | autoAlpha 1,scale 0.96 → 1,back.out(1.4),0.5s |
| 标题级联 | 0.10s 起 | 锚点词 y70 / 0.16s,其余 y40–60 / 0.12–0.16s,相邻重叠 1 帧 |
| 副标题 | 0.62s | y34 → 0,0.38s |
| 正文 | 1.00s 起 | 每段间隔 0.28s,y30 → 0,0.32s |
| 待机氛围 | 0.50s 起 | 卡片 ±6px sin 浮动 + 辉光 / 细线透明度呼吸,周期 3.2s |
6.3 关键代码
// 素材卡:无淡入、无位移,只做 scale 回弹
tl.set("#media-wrap", { autoAlpha: 1, scale: 0.96 }, T_CARD);
tl.to("#media-wrap", { scale: 1, duration: 0.5, ease: "back.out(1.4)" }, T_CARD);
// 标题 token 级联
var tokens = [
{ id: "#tk1", y: 70, d: 0.16 },
{ id: "#tk2", y: 60, d: 0.14 },
{ id: "#tk3", y: 52, d: 0.14 },
{ id: "#tk4", y: 46, d: 0.12 },
{ id: "#tk5", y: 40, d: 0.16 },
];
var t = T_TITLE;
tokens.forEach(function (k, i) {
tl.set(k.id, { opacity: 1, y: k.y }, t);
tl.to(k.id, { y: 0, duration: k.d, ease: "power4.out" }, t);
t += k.d + (i === tokens.length - 1 ? 0 : -F);
});
// 副标题
tl.set("#subtitle", { opacity: 1, y: 34 }, T_SUBTITLE);
tl.to("#subtitle", { y: 0, duration: 0.38, ease: "power4.out" }, T_SUBTITLE);
// 正文按段入场
document.querySelectorAll("#body-paras p").forEach(function (el, i) {
var at = T_BODY + i * BODY_GAP;
tl.set(el, { opacity: 1, y: 30 }, at);
tl.to(el, { y: 0, duration: 0.32, ease: "power4.out" }, at);
});
// 待机氛围(单一驱动,纯时间函数)
tl.to({}, {
duration: COMP_DURATION - T_AMBIENT,
ease: "none",
onUpdate: function () {
var w = (Math.PI * 2 / AMBIENT_PERIOD) * this.time();
floatEl.style.transform = "translateY(" + (-6 * Math.sin(w)).toFixed(2) + "px)";
glowEl.style.opacity = (0.26 + 0.08 * Math.sin(w + 1.1)).toFixed(3);
edgeA.style.opacity = (0.28 + 0.08 * Math.sin(w)).toFixed(3);
edgeB.style.opacity = (0.20 + 0.08 * Math.sin(w + Math.PI)).toFixed(3);
},
}, T_AMBIENT);
window.__timelines = window.__timelines || {};
window.__timelines["main"] = tl;
七、校验与渲染
npx --yes hyperframes@0.8.27 lint # 快速反馈
npx --yes hyperframes@0.8.27 check # lint + 运行时 + 布局 + 对比度,需全绿
npx --yes hyperframes@0.8.27 snapshot --at 0 1.5 5 10 # 关键帧自检
npx --yes hyperframes@0.8.27 render # 出片 → renders/*.mp4
check全绿,对比度按 WCAG AA。snapshot确认四层完整在画面内、标题单行、正文无孤字和半截短行。- 渲染后 ffprobe 复核:时长、帧数、帧率与素材片段一致,无音轨。
八、封面与首帧
封面从成片中取一帧,不另做设计。
- 选帧:挑素材的重点或高光画面,且四层文字完整、可读。
- 比例:平台要求 3:4。以画面中心为原点,上下等量裁切:1080×1920 → 1080×1440,上下各裁 240px。
- 首帧:把选中帧的完整 9:16 画面插到视频最前面,只占 1 帧,用于平台自动取首帧作默认封面。平台按 3:4 居中裁切后,结果就是封面图。成片总时长因此多出 1 帧。
N=540 # 选中帧序号 = 秒数 × fps
ffmpeg -i render.mp4 -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 封面
ffmpeg -loop 1 -framerate $FPS -t 1 -i first_frame.png -i render.mp4 \
-filter_complex "[0:v]format=yuv420p,setsar=1,fps=$FPS,trim=end_frame=1[a];[1:v]setsar=1[b];[a][b]concat=n=2:v=1:a=0[v]" \
-map "[v]" -r $FPS -c:v libx264 -crf 16 -pix_fmt yuv420p -movflags +faststart final.mp4
- 复核:ffprobe 帧数 = 原帧数 + 1;首帧居中裁 3:4 后与封面一致。
九、速查表
| 想改什么 | 改哪里 | 值 |
|---|---|---|
| 时长 / 帧率 | #root 与 video 的 data-* | = 素材片段实测时长 / 帧率 |
| 内容区宽 | --content-w | 880px(边距 100px) |
| 标题 | #title | 62px / 600,单行(放不下:负字距或降至约 54px) |
| 副标题 | #subtitle | 43px / 400,上间距 12px |
| 正文 | #body-paras p | 38px / 500,行高 1.45,段间距 13px |
| 素材卡间距 | .media-wrap 的 margin | 22px 0 24px |
| 素材卡比例 | .media-card video 的 aspect-ratio | = 源片比例,object-fit: contain |
| 入场节拍 | T_* / BODY_GAP | 卡 0 / 标题 0.1 / 副标题 0.62 / 正文 1.0 / 段隔 0.28 |
| 氛围周期 | AMBIENT_PERIOD | 3.2s |
| 配色 | --bg / --accent | #f7f3ea / #8f5e00 |
十、Checklist
- 文案:根据来源帖写标题 / 副标题 / 正文,观点注明人名,提交用户审核通过。
- 素材:ffmpeg 按高光拼接或整段原片模式处理(倍速、去音轨、CFR、0.25s 关键帧),ffprobe 量视频流时长与帧数。
- 工程:搭
index.html;data-duration/data-fps与素材一致;aspect-ratio填源片比例。 - 字体:按文案用字从
PingFang.ttc子集出三档 woff2,确认不缺字。 - 排版:标题拆 token 并单行;正文自然折行,消除孤字和半截短行;加
.hl。 - 校验:
lint→check全绿 →snapshot确认四层不超 1920px →render。 - 复核:ffprobe 核对成片时长、帧率、无音轨。
- 封面:选高光帧,居中裁 3:4 出封面,并把该帧完整画面插为视频首帧(1 帧)。
本文档为快讯风竖屏短视频(NewsFlash)模板的设计与开发规范,参数与流程自包含于本文。