카운트다운 비디오은(는) 무엇을 하나요?
정확히 정해진 시간에 종료되는 카운트다운을 만듭니다. 전체 제작의 핵심은 한 가지 아이디어입니다: 프레임(또는 단조 증가하는 타임스탬프)에서 남은 시간을 계산하고, 그 숫자를 포맷하고 애니메이션 처리하십시오 — 절대 setInterval로 카운트다운하지 마십시오. 라이브 스트리밍 "곧 시작합니다" 화면, 출시/세일 카운트다운, 루핑 배경을 다룹니다.
사용 시기
- "곧 시작합니다" / "잠시 후 돌아옵니다" 라이브 스트리밍 화면 (5-10분 타이머).
- 출시 및 플래시 세일 카운트다운 (목표 날짜까지 dd:hh:mm:ss; 긴급함을 위해 mm:ss).
- 숫자가 정확한 프레임에 0에 도달해야 하는 모든 애니메이션 카운터.
하나의 규칙: 클록에서 숫자를 도출하고, 틱(tick)을 사용하지 마십시오
setInterval(fn, 1000) 카운트다운은 드리프트됩니다 — 콜백은 적어도 1000ms 후에 실행되며, 정확히 실행되지 않고, 탭이 제한되거나 렌더 프레임이 느려지면 누적됩니다. 분당 약 1초 손실에 대한 보고가 일반적입니다. 해결책은 렌더링된 비디오와 라이브 웹 모두 동일합니다: 권위 있는 클록의 순수 함수로 남은 시간을 계산하여, 늦은 프레임이 오류를 누적하는 대신 다음 프레임에서 자체 수정되도록 합니다.
| 컨텍스트 | 권위 있는 클록 | 남은 시간 |
|---|---|---|
| Remotion 렌더 | useCurrentFrame() | total − frame/fps |
| 웹 (라이브 페이지) | 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"이 전체 1초 동안 표시됨
const elapsed = frame / fps;
const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));
return <div style={{ fontVariantNumeric: "tabular-nums" }}>{formatMMSS(remaining)}</div>;
};
durationInSeconds는 **속성(prop)**이며, 상수가 아닙니다 — 동일한 컴포지션이 하나의 입력 변경만으로 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를 참조하십시오.
시간 포맷팅
나눗셈 + 모듈로 연산으로 남은 초 정수를 필드로 분해한 다음, 0으로 채웁니다. 지속 시간에 맞는 형식을 선택하십시오 — 분만 중요한데 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 | 스트림 "곧 시작합니다", 짧은 타이머 (<1시간) | — |
hh:mm:ss | 플래시 세일, 당일 이벤트 | dd가 0이면 숨김 |
dd:hh:mm:ss | 출시 / 여러 날 카운트다운 | 더 깔끔하게 읽기 위해 선행 0 단위 숨김 |
항상 font-variant-numeric: tabular-nums (또는 고정폭 숫자 폰트)을 설정하여 숫자가 동일한 너비를 유지하고 숫자가 변경될 때 레이아웃이 흔들리지 않도록 하십시오.
숫자 롤 / 플립 전환
평면적인 숫자 교체는 저렴해 보입니다; 구르는 "주행 기록계" 모양이 카운트다운을 돋보이게 합니다. 각 숫자를 0-9의 수직 스트립으로 렌더링하고 -digit × digitHeight만큼 변환(translate)하십시오. 텍스트 콘텐츠가 아닌 위치를 애니메이션 처리하여 변경이 튀는 것이 아니라 미끄러지듯 움직이도록 하십시오.
.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 vs dd:hh:mm:ss); 방해가 될 때 선행 0 단위는 생략.
- 배경 루프 주기가 타임라인을 균등하게 나눔; 랩에 이음새 없음.
전달 및 검증 (렌더링된 정지 이미지 → 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 검사 — 숫자는 정확히 프레임/fps 계산을 읽어야 함:
# - 프레임 N에서 표시 = ceil(durationInSeconds - N/fps) (예: 30fps에서 300초, 프레임 4500 → 150s → "02:30")
# - 마지막 프레임은 00:00 (또는 종료 상태 유지)을 읽음; 드리프트 없음, 1초 오차 없음
# - 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를 확인한 다음, 확인하려는 모든 초에 대한 프레임을 계산하십시오.
마치기 전에:
- 정지 이미지가 프레임 0, 중간, 마지막에서 오류 없이 깔끔하게 렌더링됩니다.
- 프레임 N의 숫자 값이
ceil(durationInSeconds - N/fps)와 정확히 일치합니다; 마지막 프레임이 의도된 프레임에서 00:00에 도달합니다 (드리프트/1초 오차 없음). - 타이머 + 브랜드가 확인된 모든 프레임에서 9:16 안전 영역 내에 있습니다.
- 프레임 기반만 —
setInterval/Date.now()/ 타이머 없음;tabular-nums로 레이아웃 유지. - 제공된
durationInSeconds(단순defaultProps가 아님)가 올바르게 렌더링됩니다; MP4 인코딩, GIF는 선택 사항입니다.
참조 파일
references/countdown-cookbook.md— 완전히 실행 가능한 Remotion<Countdown>(구성 가능한 지속 시간, mm:ss / dd:hh:mm:ss, 종료 상태 유지), 시간대 처리가 포함된 고정 날짜 카운트다운 웹 컴포넌트, "곧 시작합니다" 스트림 화면 레이아웃 (브랜드 블록 + 소셜 핸들 + 루핑 그라데이션 배경), 그리고 끊김 없는 루프 배경 패턴.references/digit-flip.md— 두 가지 결정론적 숫자 애니메이션 컴포넌트: 주행 기록계 릴 (프레임에 의해 구동되는 수직 0-9 스트립) 및 플립 카드 (위쪽 절반 접힘) 효과, 그리고 각각이 출시, 세일, 스트림 중 어느 상황에 적합한지.