Communitygithub.com

Kreesten-hsh/video

该技能负责生成倒计时视频、动画、直播“即将开始”画面、活动/发售/促销倒计时等,实现帧精确、无漂移的计时器,并支持数字滚动/翻转过渡效果。

video 是什么?

本技能专注于创建帧精确、无累积误差的倒计时视频或直播画面。其核心原则是:永远从权威时钟源(如 Remotion 的 `useCurrentFrame()` 或浏览器的 `performance.now()`)推导剩余时间,而绝不使用 `setInterval` 递减计数器,从而彻底避免因帧延迟或标签页休眠导致的“漂移”问题。技能覆盖了多种场景:Remotion 渲染的 MP4 片段、基于 `requestAnimationFrame` 的网页计时器、以及面向固定目标日期的倒计时。它提供了 `mm:ss`、`hh:mm:ss` 和 `dd:hh:mm:ss` 等时间格式化函数,并确保数字等宽(`tabular-nums`)防止布局抖动。此外,还内置了数字滚动(翻页钟/里程表)动画组件,以及无缝循环背景的实现方案。该技能适用于在线直播的“即将开始”或“马上回来”画面、产品发布/闪购倒计时,以及任何要求数字在精确帧上归零的动画计数器。支持 Remotion 和纯前端 Web 平台,并附带了驱动组件、格式化工具和输出验证脚本(如生成关键帧截图、检测编码参数等),确保最终输出帧级别精准。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Kreesten-hsh/video/tree/HEAD/.agents/skills/countdown-video

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

倒计时视频

制作一个能在精确时刻结束的倒计时。整个方案只有一个核心思想:从帧(或单调时间戳)推导剩余时间,然后格式化和动画化该数字——永远不要使用 setInterval 进行递减。涵盖直播“即将开始”画面、活动/促销倒计时以及循环背景。

何时使用

  • 直播“即将开始”/“马上回来”画面,搭配 5–10 分钟计时器。
  • 产品发布和闪购倒计时(到目标日期的 dd:hh:mm:ss 格式;短时紧迫感用 mm:ss)。
  • 任何需要数字在精确帧上归零的动画计数器。

唯一规则:从时钟驱动数字,绝不使用定时器

使用 setInterval(fn, 1000) 的倒计时会漂移——回调触发时间 至少 延迟 1000ms,永远不精确,且当标签页被节流或渲染帧缓慢时会累积误差。常见报告是每分钟丢失约 1 秒。对于渲染视频和实时网页,解决方案相同:将剩余时间作为权威时钟的纯函数来计算,这样迟到的帧会在下一帧自行修正,而不是累积误差。

上下文权威时钟剩余时间
Remotion 渲染useCurrentFrame()total − frame/fps
Web(实时页面)performance.now() 或 Date.now()targetMs − now
固定日期Date.now()targetTimestamp − Date.now()

永远不要将剩余时间存入状态并递减它。每帧都重新计算。

帧精确倒计时(Remotion → MP4)

useCurrentFrame() 是确定性的:第 N 帧总是在精确的 N/fps 秒处渲染。从总时长中减去它,计时器天然无漂移。

import { useCurrentFrame, useVideoConfig } from "remotion";

export const Countdown: React.FC<{ durationInSeconds: number }> = ({
  durationInSeconds,
}) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  // 剩余秒数,下限为 0 — 使用 ceil 使“1”显示完整一秒
  const elapsed = frame / fps;
  const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));

  return <div style={{ fontVariantNumeric: "tabular-nums" }}>{formatMMSS(remaining)}</div>;
};

durationInSeconds 是一个属性,而非常量——同一个组合通过更改一个输入即可渲染 5 分钟的直播计时器或 30 秒的促销计时器。将组合的 durationInFrames 设置为 durationInSeconds * fps,使视频时长与倒计时完全一致。

网页计时器(实时页面,requestAnimationFrame)

对于实时页面,用 performance.now() 驱动 rAF 循环,并且仅在显示的秒数变化时才重绘(不会为 1Hz 的数字每秒工作 60 次):

const start = performance.now();
let lastShown = -1;
function tick(now) {
  const remaining = Math.max(0, Math.ceil(duration - (now - start) / 1000));
  if (remaining !== lastShown) { render(remaining); lastShown = remaining; }
  if (remaining > 0) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

固定日期倒计时与 Date.now() 思路相同:Math.max(0, target - Date.now())。完整的带时区处理的日期目标版本请参阅 references/countdown-cookbook.md。

时间格式化

将剩余秒数整数通过除法和取模分解为各个字段,然后补零。选择适合时长的格式——当分钟就足够时,绝不显示 00d 00h 05m。

const pad = (n) => String(n).padStart(2, "0");
const formatMMSS = (s) => `${pad(Math.floor(s / 60))}:${pad(s % 60)}`;
const formatDHMS = (s) => {
  const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600);
  const m = Math.floor((s % 3600) / 60), sec = s % 60;
  return `${pad(d)}:${pad(h)}:${pad(m)}:${pad(sec)}`;
};
格式用途何时删除字段
mm:ss直播“即将开始”、短计时器(<1h)—
hh:mm:ss闪购、同日活动当 dd 为 0 时隐藏
dd:hh:mm:ss发布/多日倒计时隐藏前导零单位以获得更干净的显示

始终设置 font-variant-numeric: tabular-nums(或等宽数字字体),使数字保持等宽,布局不会随数字变化而抖动。

数字滚动/翻转过渡

平白的数字跳变显得廉价;滚动“里程表”外观更能体现倒计时氛围。将每个数字渲染为 0–9 的垂直条带,并通过 -digit × digitHeight 进行平移。对位置进行动画,而不是文本内容,使变化是滑动而非跳跃。

.reel { height: 1em; overflow: hidden; }
.reel .col { transition: transform .45s cubic-bezier(.22,1,.36,1); }
/* col 容纳堆叠的 0..9;按 -value em 偏移 */

在 Remotion 中,通过帧驱动条带偏移而非 CSS 过渡(使其确定)。翻牌风格(上半部分折叠到下半部分)和每个数字的卷轴两种效果,以及可运行组件,均在 references/digit-flip.md 中有详细说明。

循环背景

直播画面会持续数分钟,因此背景必须无缝循环。构建周期能整除时间线的运动(例如渐变或粒子漂移,周期为 loopFrames),并通过 frame % loopFrames 读取,使衔接处无可见接缝。保持低对比度和慢速——数字是主体,背景是氛围。references/countdown-cookbook.md 中有配方。

输出检查清单

  • 每帧从 useCurrentFrame() / performance.now() 重新计算剩余时间——无 setInterval 递减。
  • 计时器在精确的目标帧上达到 00:00;durationInFrames === durationInSeconds × fps。
  • 时长是输入属性,而非硬编码常量。
  • 数字使用 tabular-nums;布局不随数字变化而移动。
  • 格式与跨度匹配(mm:ss 与 dd:hh:mm:ss);前导零单位在分散注意力时去掉。
  • 背景循环周期整除时间线;衔接处无接缝。

交付与验证(渲染静态帧 → MP4)

打包好的辅助脚本(scripts/):使用 scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png 拼接静态帧,然后用 scripts/probe-mp4.sh out.mp4 [WxH] [fps] 断言编码。参见 scripts/README.md。

倒计时应以 Remotion 组合形式交付(<Composition> + zod schema + defaultProps,durationInSeconds 为属性)——计时器是 useCurrentFrame() 的纯函数,绝不使用 setInterval / Date.now() / Math.random()。设置 durationInFrames = durationInSeconds * fps。交付物 = out/*.mp4 + 项目。默认竖屏 9:16(1080×1920)。

验证循环——渲染静态帧 → 检查 → 编码。 重点是帧精确,因此在编码前检查精确帧上的数字值。

# 在开始/中间/结束位置的静态帧——使用交付的属性(将要渲染的真实时长)
npx remotion still Countdown out/f-start.png --frame=0   --props='{"durationInSeconds":300}'
npx remotion still Countdown out/f-mid.png   --frame=N   --props='{"durationInSeconds":300}'
npx remotion still Countdown out/f-end.png   --frame=L   --props='{"durationInSeconds":300}'  # L = durationInFrames-1

# 检查每个 PNG——数字必须准确显示 frame/fps 计算的结果:
#  - 在第 N 帧,显示值 = ceil(durationInSeconds - N/fps) (例如 300s @ 30fps,第 4500 帧 → 150s → "02:30")
#  - 最后一帧显示 00:00(或结束状态保持);无漂移,无差一秒错误
#  - tabular-nums:布局在帧间不抖动
#  - 9:16 安全区域:计时器+品牌块避开顶部约 12% 和底部约 20-35% 的 UI 及右侧操作栏

npx remotion render Countdown out/countdown.mp4 --props='{"durationInSeconds":300}'   # 数字验证通过后再编码
npx remotion render Countdown out/demo.gif --codec=gif                                 # README 演示片段

使用 npx remotion compositions 确认 durationInFrames/fps,然后计算你想要检查的任何秒数对应的帧。

完成前:

  1. 第 0 帧、中间帧和最后一帧的静态帧渲染清晰无误。
  2. 第 N 帧的数字值精确等于 ceil(durationInSeconds - N/fps);最后一帧在预期帧上达到 00:00(无漂移/无差一秒)。
  3. 在每个检查帧中,计时器+品牌块都在 9:16 安全区域内。
  4. 仅由帧驱动——无 setInterval / Date.now() / 定时器;使用 tabular-nums 保持布局稳定。
  5. 交付的 durationInSeconds(不仅仅是 defaultProps)正确渲染;MP4 已编码,GIF 可选。

参考文件

  • references/countdown-cookbook.md——完整可运行的 Remotion <Countdown>(可配置时长,mm:ss / dd:hh:mm:ss,结束状态保持),带时区处理的固定日期倒计时网页组件,“即将开始”直播画面布局(品牌块+社交链接+循环渐变背景),以及无缝循环背景模式。
  • references/digit-flip.md——两个确定性的数字动画组件:里程表卷轴(由帧驱动的垂直 0–9 条带)和翻牌(上半部分折叠)效果,以及各自适合发布、促销还是直播场景的说明。

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

相关技能