关于 countdown-video
countdown-video 是用于制作 Remotion 视频和网页倒计时的技能,面向视频制作者与网页开发者。它按帧或时钟计算剩余时间以避免计时漂移,并涵盖数字动画、循环背景和输出检查。
主要功能
- 01按帧驱动 Remotion 倒计时
- 02基于时钟的网页计时器
- 03可配置时间格式
- 04数字滚轮与翻页效果
- 05无缝循环背景
- 06渲染与 MP4 验证
适用场景
- 01直播即将开始画面
- 02发布与限时促销倒计时
- 03指定日期倒计时
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ iart-ai/ tiktok-video-skills --skill countdown-video安装后在 Claude Code 中输入/countdown-video确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/countdown-cookbook.mddigit-flip.md详细介绍
倒计时视频
制作一个恰好在预定时刻结束的倒计时。整个技巧只有一个核心:根据帧(或单调时间戳)计算剩余时间,然后格式化并为数字添加动画——绝不要用 setInterval 倒计时。适用于直播“即将开始”画面、发布/促销倒计时和循环背景。
适用场景
- 带 5–10 分钟计时器的“即将开始”/“马上回来”直播画面。
- 发布和限时促销倒计时(从 dd:hh:mm:ss 倒数到目标日期;紧迫感倒计时用 mm:ss)。
- 任何要求数字恰好在某一帧归零的动画计数器。
唯一规则:数字由时钟驱动,绝不依赖计时滴答
setInterval(fn, 1000) 倒计时会产生漂移——回调至少会在 1000 ms 后触发,而非恰好在此时触发;如果标签页受到节流或渲染帧较慢,还会不断累积偏差。每分钟少掉约 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();
// remaining seconds, clamped at 0 — ceil so "1" shows for its full second
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,这样视频长度就与倒计时完全一致。
Web 计时器(实时页面,requestAnimationFrame)
对于实时页面,使用 performance.now() 驱动 rAF 循环,并且只在显示的秒数发生变化时重绘(对于 1 Hz 的数字,无需每秒工作 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 | 发布/多日倒计时 | 隐藏前导的 0 单位,让显示更简洁 |
始终设置 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 holds stacked 0..9; shift by -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)。
验证流程——渲染静帧 → 检查 → 编码。 核心要求是帧精确,因此编码前请检查精确帧上的数字值。
# Stills at start / mid / end — WITH SHIPPED PROPS (the real duration you'll render)
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
# Inspect each PNG — the digits must read EXACTLY the frame/fps math:
# - at frame N, displayed = ceil(durationInSeconds - N/fps) (e.g. 300s @ 30fps, frame 4500 → 150s → "02:30")
# - last frame reads 00:00 (or the end-state hold); no drift, no off-by-one second
# - tabular-nums: layout does not jitter between frames
# - 9:16 safe area: the timer + brand block sit clear of top ~12% and bottom ~20-35% UI and the right action rail
npx remotion render Countdown out/countdown.mp4 --props='{"durationInSeconds":300}' # encode once digits verify
npx remotion render Countdown out/demo.gif --codec=gif # README proof clip
使用 npx remotion compositions 确认 durationInFrames / fps,然后计算要检查的任意秒数对应的帧。
完成前请确认:
- 第 0 帧、中间帧和最后一帧均能正常渲染静帧——没有错误。
- 第 N 帧的数字值精确等于
ceil(durationInSeconds - N/fps);最后一帧在目标帧显示 00:00(没有漂移或偏差一帧)。 - 在每个检查帧上,计时器和品牌内容均处于 9:16 安全区域内。
- 仅由帧驱动——不使用
setInterval/Date.now()/ 计时器;使用tabular-nums保持布局稳定。 - 实际交付的
durationInSeconds(而非仅defaultProps)能够正确渲染;MP4 已编码,GIF 可选。
参考文件
references/countdown-cookbook.md— 完整可运行的 Remotion<Countdown>(可配置时长、mm:ss / dd:hh:mm:ss、结束状态保持)、支持时区处理的固定日期倒计时 Web 组件、“即将开始”直播屏幕布局(品牌区块 + 社交账号 + 循环渐变背景),以及无缝循环背景图案。references/digit-flip.md— 两种确定性的数字动画组件:里程表滚轮(由帧驱动的垂直 0–9 条带)和翻页卡片(上半部分折叠)效果,并说明它们分别适用于发布、促销和直播的场景。