ライブデモ Live Demo
以下はこのテンプレートから Remotion でレンダーした実 mp4 です。手元で同じ動画を再生成できます(→ Commands セクション参照)。
The video below was rendered from this template using Remotion. Regenerate it locally with the commands in the Commands section.
AI向け説明 AI Description
`R-003` は 4 枚の KPI スタットカードがスタガーで登場しながら数値がカウントアップするダッシュボード紹介シーンを生成する Remotion テンプレートです。 1920×1080 / 30fps / 10秒(300フレーム)で、次のシーン進行を自動で再現します: ①ページタイトル「Dashboard Overview」が spring でフェードイン → ②「Total Revenue($124,500)」カードが spring でスライドアップしながら数値がカウントアップ → ③「Active Users(8,340)」カードが同様に登場 → ④「Conversion Rate(3.7%)」カードが登場 → ⑤「Monthly Orders(2,847)」カードが登場 → ⑥全4カードが表示された状態でホールド。 各カードには左端のアクセントカラーストリップ・アイコン・ラベル・数値・トレンドバッジが含まれます。 数値カウントアップは ease-out quad で補間し、`interpolate` + `Easing.out(Easing.quad)` で実装しています。 差し替えポイントは STATS 配列と theme の 2 箇所のみです。
`R-003` is a Remotion template that renders a dashboard introduction scene where 4 KPI stat cards reveal in staggered sequence with number count-up animations. 1920×1080 / 30fps / 10s (300 frames), with this automatic scene progression: ① Page title "Dashboard Overview" spring-fades in → ② "Total Revenue ($124,500)" card spring-slides up with number counting up → ③ "Active Users (8,340)" card appears similarly → ④ "Conversion Rate (3.7%)" card appears → ⑤ "Monthly Orders (2,847)" card appears → ⑥ all 4 cards held visible. Each card includes a left-edge accent color strip, icon, label, large value, and trend badge. Count-up uses ease-out quad interpolation via `interpolate` + `Easing.out(Easing.quad)`. The two swap points are: the STATS array and theme.
調整可能パラメータ Adjustable Parameters
- fps — フレームレート(デフォルト: 30)Frame rate (default: 30)
- durationSec — 動画の長さ(デフォルト: 10秒 = 300フレーム)Video duration (default: 10s = 300 frames)
- cardStagger — カード間の登場フレーム差(デフォルト: 50 = 1.67秒)。増やすとゆっくり順番に登場、減らすと一気に登場Frames between each card's appearance (default: 50 = 1.67s). Increase for slower stagger, decrease for faster
- countDuration — 数値カウントアップにかけるフレーム数(デフォルト: 60 = 2秒)Frames for the count-up animation (default: 60 = 2s)
- STATS[].value — 各KPIのカウントアップ終了値Target value for each KPI count-up
- STATS[].format — 'currency'($付き整数)/ 'percent'(小数1桁%)/ 'number'(整数カンマ区切り)'currency' ($-prefixed integer) / 'percent' (1-decimal %) / 'number' (comma-separated integer)
- STATS[].trend — トレンドバッジに表示するテキスト(例: '12.4%')Text shown in the trend badge (e.g. '12.4%')
- STATS[].accentColor — 左端ストリップとアイコン背景のアクセントカラーAccent color for the left strip and icon background
- theme.bg — 動画の背景色(デフォルト: #f8fafc)Video background color (default: #f8fafc)
- theme.panel — アプリバー・カード背景色(デフォルト: #ffffff)App bar and card background color (default: #ffffff)
- theme.radius — カードの角丸 px(デフォルト: 16)Card corner radius in px (default: 16)
実装 Implementation
以下のファイルを新規プロジェクトに配置してください。
Place the following files in a new project.
import { registerRoot } from 'remotion';
import { RemotionRoot } from './Root';
registerRoot(RemotionRoot);
import React from 'react';
import { Composition } from 'remotion';
import { DashboardReveal, dashboardRevealSchema } from './compositions/DashboardReveal';
const defaultTheme = {
bg: '#f8fafc',
panel: '#ffffff',
border: '#e2e8f0',
text: '#0f172a',
muted: '#64748b',
radius: 16,
};
export const RemotionRoot: React.FC = () => (
<>
{/* ── Landscape 16:9 ──────────────────────────────── */}
<Composition
id="DashboardReveal"
component={DashboardReveal}
schema={dashboardRevealSchema}
durationInFrames={300}
fps={30}
width={1920}
height={1080}
defaultProps={{ cardStagger: 50, countDuration: 60, theme: defaultTheme }}
/>
{/* ── Vertical 9:16 (Stories / Reels) ─────────────── */}
<Composition
id="DashboardRevealVertical"
component={DashboardReveal}
schema={dashboardRevealSchema}
durationInFrames={300}
fps={30}
width={1080}
height={1920}
defaultProps={{ cardStagger: 50, countDuration: 60, theme: defaultTheme }}
/>
</>
);
import React from 'react';
import {
AbsoluteFill,
Easing,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
import { z } from 'zod';
import { StatCard } from '../components/StatCard';
import '../styles.css';
// ─── Schemas ────────────────────────────────────────────────────────────────
const themeSchema = z.object({
bg: z.string(),
panel: z.string(),
border: z.string(),
text: z.string(),
muted: z.string(),
radius: z.number(),
});
export const dashboardRevealSchema = z.object({
cardStagger: z.number().min(10).max(90),
countDuration: z.number().min(20).max(120),
theme: themeSchema,
});
type Props = z.infer<typeof dashboardRevealSchema>;
// ─── KPI data (replace with your own) ───────────────────────────────────────
export type StatFormat = 'currency' | 'number' | 'percent';
export interface StatDef {
id: number;
icon: string;
label: string;
value: number;
format: StatFormat;
trend: string;
trendUp: boolean;
accentColor: string;
}
const STATS: StatDef[] = [
{ id: 1, icon: '💰', label: 'Total Revenue', value: 124500, format: 'currency', trend: '12.4%', trendUp: true, accentColor: '#4f46e5' },
{ id: 2, icon: '👥', label: 'Active Users', value: 8340, format: 'number', trend: '5.2%', trendUp: true, accentColor: '#0891b2' },
{ id: 3, icon: '📈', label: 'Conversion Rate', value: 3.7, format: 'percent', trend: '0.8%', trendUp: true, accentColor: '#16a34a' },
{ id: 4, icon: '📦', label: 'Monthly Orders', value: 2847, format: 'number', trend: '18.3%', trendUp: true, accentColor: '#ea580c' },
];
// ─── Composition ─────────────────────────────────────────────────────────────
export const DashboardReveal: React.FC<Props> = ({ cardStagger, countDuration, theme }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Title entrance
const titleSpring = spring({ frame, fps, config: { damping: 22, stiffness: 90, mass: 0.8 } });
const titleOpacity = interpolate(titleSpring, [0, 0.5], [0, 1], { extrapolateRight: 'clamp' });
const titleY = interpolate(titleSpring, [0, 1], [24, 0]);
// Subtitle fades in slightly after title
const subOpacity = interpolate(frame, [10, 30], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.quad),
});
// Compute per-card appear frames from cardStagger
const statsWithFrames = STATS.map((s, i) => ({
...s,
appearFrame: 20 + i * cardStagger,
}));
return (
<AbsoluteFill style={{
background: theme.bg,
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
}}>
{/* ── App bar ─────────────────────────────────────────────────────── */}
<div style={{
position: 'absolute', top: 0, left: 0, right: 0,
height: 72,
background: theme.panel,
borderBottom: `1.5px solid ${theme.border}`,
display: 'flex', alignItems: 'center',
padding: '0 80px', gap: 40,
zIndex: 1,
}}>
<span style={{ fontSize: 26, fontWeight: 800, color: theme.text, letterSpacing: '-0.03em' }}>
◆ MyApp
</span>
{['Dashboard', 'Reports', 'Settings'].map((label) => (
<span key={label} style={{ fontSize: 18, color: theme.muted, fontWeight: 500 }}>
{label}
</span>
))}
</div>
{/* ── Page content ─────────────────────────────────────────────────── */}
<div style={{
position: 'absolute', top: 72, left: 0, right: 0, bottom: 0,
padding: '52px 80px',
display: 'flex', flexDirection: 'column', gap: 36,
}}>
{/* Page title */}
<div style={{ opacity: titleOpacity, transform: `translateY(${titleY}px)` }}>
<h1 style={{
fontSize: 44, fontWeight: 800, color: theme.text,
margin: 0, letterSpacing: '-0.02em',
}}>
Dashboard Overview
</h1>
<p style={{
fontSize: 20, color: theme.muted, margin: '8px 0 0',
opacity: subOpacity,
}}>
Q1 2025 — Updated just now
</p>
</div>
{/* 2×2 stat card grid */}
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gridTemplateRows: '1fr 1fr',
gap: 28,
flex: 1,
}}>
{statsWithFrames.map((stat) =>
frame >= stat.appearFrame ? (
<StatCard
key={stat.id}
frame={frame}
fps={fps}
appearFrame={stat.appearFrame}
icon={stat.icon}
label={stat.label}
value={stat.value}
format={stat.format}
trend={stat.trend}
trendUp={stat.trendUp}
accentColor={stat.accentColor}
countDuration={countDuration}
theme={theme}
/>
) : (
<div key={stat.id} />
)
)}
</div>
</div>
</AbsoluteFill>
);
};
import React from 'react';
import { Easing, interpolate, spring } from 'remotion';
import type { StatFormat } from '../compositions/DashboardReveal';
interface Theme {
panel: string;
border: string;
text: string;
muted: string;
radius: number;
}
interface StatCardProps {
frame: number;
fps: number;
appearFrame: number;
icon: string;
label: string;
value: number;
format: StatFormat;
trend: string;
trendUp: boolean;
accentColor: string;
countDuration: number;
theme: Theme;
}
// ── Value formatting ──────────────────────────────────────────────────────────
function formatValue(raw: number, format: StatFormat): string {
if (format === 'currency') return '$' + Math.floor(raw).toLocaleString('en-US');
if (format === 'percent') return raw.toFixed(1) + '%';
return Math.floor(raw).toLocaleString('en-US');
}
export const StatCard: React.FC<StatCardProps> = ({
frame, fps, appearFrame,
icon, label, value, format,
trend, trendUp, accentColor,
countDuration, theme,
}) => {
const relFrame = frame - appearFrame;
// ── Entrance: spring slide-up ─────────────────────────────────────────────
const entranceSpring = spring({
frame: relFrame,
fps,
config: { damping: 20, stiffness: 80, mass: 0.85 },
});
const slideY = interpolate(entranceSpring, [0, 1], [40, 0]);
const opacity = interpolate(entranceSpring, [0, 0.4], [0, 1], { extrapolateRight: 'clamp' });
// ── Count-up ──────────────────────────────────────────────────────────────
const countProgress = interpolate(relFrame, [0, countDuration], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.quad),
});
const displayValue = formatValue(value * countProgress, format);
// ── Trend badge colors ────────────────────────────────────────────────────
const trendBg = trendUp ? '#dcfce7' : '#fee2e2';
const trendColor = trendUp ? '#15803d' : '#991b1b';
return (
<div style={{
opacity,
transform: `translateY(${slideY}px)`,
background: theme.panel,
border: `1.5px solid ${theme.border}`,
borderRadius: theme.radius,
padding: '36px 44px 36px 48px',
display: 'flex',
flexDirection: 'column',
gap: 20,
boxShadow: '0 4px 24px rgba(0,0,0,0.06)',
position: 'relative',
overflow: 'hidden',
}}>
{/* Left accent strip */}
<div style={{
position: 'absolute',
top: 0, left: 0,
width: 6, height: '100%',
background: accentColor,
}} />
{/* Icon + label */}
<div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
<div style={{
width: 52, height: 52,
borderRadius: 14,
background: accentColor + '1a',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 28,
flexShrink: 0,
}}>
{icon}
</div>
<span style={{ fontSize: 20, color: theme.muted, fontWeight: 500 }}>
{label}
</span>
</div>
{/* Big value (count-up) */}
<div style={{
fontSize: 56, fontWeight: 800,
color: theme.text,
lineHeight: 1,
letterSpacing: '-0.03em',
fontVariantNumeric: 'tabular-nums',
}}>
{displayValue}
</div>
{/* Trend badge */}
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: 6,
background: trendBg,
color: trendColor,
borderRadius: 20,
padding: '6px 16px',
fontSize: 18,
fontWeight: 600,
alignSelf: 'flex-start',
}}>
{trendUp ? '↑' : '↓'} {trend}
</div>
</div>
);
};
/* Global reset for Remotion canvas */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/*
* Font strategy
* ──────────────────────────────────────────────────────────────────────────
* Default: system-ui — no network dependency, safe for Lambda / CI rendering.
*
* To use Inter in local Remotion Studio, uncomment the @import below.
* For Lambda / serverless rendering, use @remotion/google-fonts or place
* Inter.woff2 in public/ and reference it via staticFile().
*
* @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
*/
import { Config } from '@remotion/cli/config';
Config.setVideoImageFormat('jpeg');
Config.setOverwriteOutput(true);
{
"scripts": {
"remotion:preview": "remotion studio",
"remotion:render": "remotion render DashboardReveal out/DashboardReveal.mp4 --codec h264"
},
"dependencies": {
"remotion": "^4.0.0",
"@remotion/cli": "^4.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"zod": "^3.22.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"typescript": "^5.0.0"
}
}
コマンド Commands
# Remotion 公式スキャフォルドから始める
npx create-video@latest
# プロンプトで "Hello World" か "Blank" を選択し、上記ファイルを上書きする
npm install remotion @remotion/cli zod
npx remotion studio
# ブラウザで http://localhost:3000 が開きます
# フレームをスクラブしてアニメーションを確認できます
# ── Step 1: Remotion プロジェクト内でレンダー
npx remotion render DashboardReveal out/R-003.mp4 --codec h264
# → out/R-003.mp4 (1920×1080 / 30fps / H.264)
# ── Step 2: DevSnips の assets/ に配置
# (DevSnips リポジトリのルートで実行)
mkdir -p src/assets/remotion
cp /path/to/remotion-project/out/R-003.mp4 src/assets/remotion/R-003.mp4
# ── Step 3: poster 画像を生成(ffmpeg がある場合)
ffmpeg -i src/assets/remotion/R-003.mp4 \
-ss 00:00:05.000 -frames:v 1 \
src/assets/remotion/R-003.png
# ── Step 4: DevSnips をビルド & デプロイ
npm run build
# → dist/assets/remotion/R-003.mp4 として
# https://devsnips.dev/assets/remotion/R-003.mp4 で配信されます
# Root.tsx に DashboardRevealVertical が定義済み (1080×1920)
npx remotion render DashboardRevealVertical out/R-003-vertical.mp4 --codec h264
# poster 生成
ffmpeg -i out/R-003-vertical.mp4 \
-ss 00:00:05.000 -frames:v 1 \
out/R-003-vertical.png
注意とバリエーション Notes & Variations
- KPI の差し替え: `STATS` 配列の各オブジェクトを変更するだけで内容を入れ替えられます。`value`・`format`・`trend`・`accentColor`・`icon`・`label` の 6 フィールドが差し替え対象です。小数値の場合は `format: 'percent'` を使い `value: 3.7` のように指定してください。 Swapping KPI data: edit entries in the STATS array to customize. The six fields to swap are: `value`, `format`, `trend`, `accentColor`, `icon`, and `label`. For decimal values, use `format: 'percent'` and set e.g. `value: 3.7`.
- カード数の変更: STATS 配列の要素を増減するだけです。3枚なら `gridTemplateColumns: '1fr 1fr 1fr'`・`gridTemplateRows: '1fr'` に変更してください。6枚なら 2×3 グリッドにして `durationInFrames` を伸ばしてください(目安: カード1枚あたり +50フレーム)。 Changing the number of cards: add or remove items in the STATS array. For 3 cards use `gridTemplateColumns: '1fr 1fr 1fr'` / `gridTemplateRows: '1fr'`. For 6 cards use a 2×3 grid and extend `durationInFrames` (approx. +50 frames per additional card).
- 縦動画でのレイアウト: `DashboardRevealVertical`(1080×1920)コンポジションを使う場合、カードを縦1列(`gridTemplateColumns: '1fr'`・4行)にすると各カードが大きく表示されます。 Vertical video layout: when using the `DashboardRevealVertical` (1080×1920) composition, consider switching to a single-column layout (`gridTemplateColumns: '1fr'`, 4 rows) for larger cards.
-
フォント: デフォルトは `system-ui`(ネットワーク依存なし)。ローカル Remotion Studio で Inter を使いたい場合は styles.css の `@import` コメントを外してください。Lambda 等のサーバーレスレンダーでは
@remotion/google-fontsパッケージか、public/にフォントファイルを置いてstaticFile()で参照する方法を推奨します。 Fonts: defaults tosystem-ui(no network dependency). Uncomment the@importin styles.css for Inter in Remotion Studio. For Lambda or serverless rendering, use the@remotion/google-fontspackage or place a font file inpublic/and reference it viastaticFile(). - Remotion Studio でのパラメータ調整: `dashboardRevealSchema` を zod で定義しているため、Remotion Studio の GUI から `cardStagger`・`countDuration`・`theme` をリアルタイムに変更できます。コードを書かなくてもカスタマイズ可能です。 Parameter tuning in Remotion Studio: because `dashboardRevealSchema` is defined with zod, Remotion Studio generates a GUI panel where you can tweak `cardStagger`, `countDuration`, and `theme` in real time without touching code.