remotion-candlestick について
RemotionでS&P 500のローソク足アニメーションを作るスキルです。動画シーンの制作者向けに、データ取得、プラグイン構成、フレーム単位のアニメーション、レンダリング検証を説明します。
主な機能
- 01Yahoo FinanceからOHLCデータを取得
- 02Remotionシーンプラグインの構成
- 03フレーム単位でアニメーション状態を計算
- 04Canvasでローソク足を描画
- 05コンポジションのプレビューとレンダリング
活用シーン
- 01S&P 500の週足ローソク足アニメーション制作
- 02カスタムシーンをRemotionコンポジションに登録
- 03高DPI画面でのCanvasのぼやけを調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vibe-motion/ skills --skill remotion-candlestickインストール後、Claude Code で/remotion-candlestickを入力して読み込まれたか確認します。
詳細
Remotion ローソク足(Candlestick)アニメーション制作手順
このスキルでは、vibe-motion-app という Remotion プロジェクトで S&P 500 のローソク足チャートを作成する手順をすべて記録しています。
前提条件
- Remotion 4.0.438 プロジェクトで、React 19 + TailwindCSS v4 が設定済み
- プラグインアーキテクチャ:
motion/でシーンを定義し、remotion/で Remotion の合成パイプラインを処理 - パッケージマネージャーとして pnpm を使用
手順 1:金融データを取得する
Yahoo Finance は、無料で過去の OHLC データを提供しています(API Key は不要です)。
scripts/fetch-sp500-data.mjs を作成します。
// 核心:调用 Yahoo Finance Chart API
const url = `https://query1.finance.yahoo.com/v8/finance/chart/%5EGSPC?period1=${PERIOD1}&period2=${PERIOD2}&interval=1wk`;
const resp = await fetch(url);
const json = await resp.json();
const result = json.chart?.result?.[0];
const ts = result.timestamp;
const quote = result.indicators.quote[0];
// 提取 { t: timestamp, o: open, h: high, l: low, c: close }
public/data/sp500-weekly.json に保存し、Remotion から staticFile() で読み込みます。
パラメーターの説明:
%5EGSPC= S&P 500 の Yahoo シンボル(^GSPCを URL エンコードしたもの)interval=1wk= 週足。1d、1moも指定できますperiod1/period2= Unix タイムスタンプ。約 40 年分(1984-2026)をカバーします
手順 2:プラグインのファイル構成を作成する
各 Remotion プラグインには、4 つの主要ファイルとレンダリングプリセットが必要です。
motion/插件名/
config.js → 默认参数
timeline.js → 场景上下文 + 逐帧动画计算
Scene.jsx → React 组件(Canvas 绘制)
plugin.js → 插件注册对象
render-presets/
default.json → 渲染预设
2a. config.js — デフォルトパラメーター
export const DEFAULT_LAYOUT_PARAMS = Object.freeze({
videoWidth: 1920, // 16:9 横屏
videoHeight: 1080,
});
export const DEFAULT_ANIMATION_PARAMS = Object.freeze({
durationSeconds: 28, // 动画总时长
title: "S&P 500",
subtitle: "过去40年 · 周线K线图",
});
export const DEFAULT_MOTION_PROPS = Object.freeze({
...DEFAULT_LAYOUT_PARAMS,
...DEFAULT_ANIMATION_PARAMS,
});
2b. timeline.js — アニメーションロジック
次の 3 つの主要な関数を実装する必要があります。
// 1. 场景上下文:验证 + 强制类型转换所有参数
export const resolveXxxSceneContext = (pluginParams) => { ... }
// 2. 时长计算:durationSeconds * fps
export const getXxxDurationInFrames = ({ fps, sceneContext }) => { ... }
// 3. 逐帧 props:从 frame/fps 计算 progress 等动画值
export const buildXxxSceneProps = ({ frame, fps, loop, sceneContext }) => { ... }
重要な設計原則:すべてのアニメーション状態は frame パラメーターのみに依存するため、Remotion の並列レンダリング要件を満たせます。
2c. Scene.jsx — Canvas 描画コンポーネント
多数の DOM 要素ではなく <canvas> を使用すると、パフォーマンスが向上します。
データの読み込みパターン:
const [dataHandle] = useState(() => delayRender("Loading data"));
useEffect(() => {
fetch(staticFile("data/xxx.json"))
.then(r => r.json())
.then(data => { setData(data); continueRender(dataHandle); })
.catch(err => cancelRender(err));
}, [dataHandle]);
Canvas 描画の主要ロジック:
const draw = useCallback(() => {
const ctx = canvas.getContext("2d");
const dpr = window.devicePixelRatio || 1;
// 高清屏适配
canvas.width = W * dpr;
canvas.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
// 1. 背景填充
// 2. 网格线 + Y轴价格刻度
// 3. 蜡烛绘制(wick + body)
// 4. X轴年份标签
// 5. 标题/副标题/统计信息
// 6. 进度条
}, [data, progress, ...]);
useEffect(() => { draw(); }, [draw]);
ローソク足の描画に関する主要パラメーター:
const CANDLE_GAP_RATIO = 0.28; // 蜡烛间距比例
const CANDLE_FADE_FRAMES = 22; // 入场动画帧数
const MAX_CANDLE_W = 8; // 最大蜡烛宽度(按缩放后)
const MIN_CANDLE_W = 0.6; // 最小蜡烛宽度
// 蜡烛宽度随可见数量自适应
const slotW = plotWidth / visibleCount;
const bodyW = Math.max(MIN_CANDLE_W, Math.min(MAX_CANDLE_W * scale, slotW * (1 - GAP_RATIO)));
入場アニメーション(フェードイン+下から伸びる動き):
const appearFrame = (i / (N - 1)) * revealEndFrame;
const candleAge = currentFrame - appearFrame;
const fadeAlpha = Math.min(1, Math.max(0, candleAge / CANDLE_FADE_FRAMES));
// 生长动画
if (candleAge < CANDLE_FADE_FRAMES && candleAge > 0) {
const growT = easeOutCubic(candleAge / CANDLE_FADE_FRAMES);
displayBodyBottom = lerp(lowY, bodyBottom, growT);
}
ctx.globalAlpha = fadeAlpha;
// ... 绘制 wick + body ...
ctx.globalAlpha = 1;
ダークテーマの色:
const COLORS = {
bg: "#1a1a19",
grid: "#2c2c2a",
upBorder: "#e66767", // 阳线边框(红)
upFill: "rgba(230,103,103,0.15)", // 阳线填充(半透明)
downFill: "#199e70", // 阴线填充(绿)
textPrimary: "#ffffff",
textSecondary: "#c3c2b7",
textMuted: "#898781",
accent: "#3987e5",
};
2d. plugin.js — プラグインの登録
export const xxxPlugin = Object.freeze({
id: "插件ID",
defaultProps: DEFAULT_MOTION_PROPS,
SceneComponent: XxxScene,
waitForSceneLayoutReady: true, // 等待数据加载完毕
resolveSceneContext: (params) => ...,
getDurationInFrames: ({ fps, sceneContext }) => ...,
buildSceneProps: ({ frame, fps, loop, sceneContext }) => ...,
getLayout: ({ sceneContext }) => sceneContext.layout,
});
waitForSceneLayoutReady: true を設定すると、Scene コンポーネントが onAutoLayoutReady() を呼び出してからフレームのレンダリングが始まります。
2e. render-presets/default.json
{
"title": "S&P 500",
"subtitle": "过去40年 · 周线K线图",
"videoWidth": 1920,
"videoHeight": 1080,
"durationSeconds": 28
}
手順 3:コンポジションを登録する
motion/project.js を編集し、ACTIVE_COMPOSITION を新しいプラグインに切り替えます。
import { xxxPlugin } from "./xxx/plugin.js";
export const ACTIVE_COMPOSITION = Object.freeze({
id: "SP500Kline",
fps: 30,
plugin: xxxPlugin,
});
手順 4:検証してレンダリングする
# 验证合成注册
pnpm run remotion:compositions
# 启动 Studio 预览(支持时间轴拖拽、参数调整)
pnpm run dev
# 渲染单帧截图
npx remotion still SP500Kline out/frame.png --frame=420
# 渲染完整视频
npx remotion render SP500Kline out/output.mp4
# 使用预设渲染
REMOTION_PROPS_FILE=motion/xxx/render-presets/default.json pnpm run remotion:render
よくある問題
Yahoo Finance API の CORS 問題
Remotion のレンダリング環境(Node.js のヘッドレスブラウザー)では、CORS の問題は発生しません。
ブラウザーのプレビューで CORS エラーが発生する場合は、staticFile() を使って、事前に取得したローカル JSON を読み込みます。
高 DPI 画面で Canvas がぼやける
必ず devicePixelRatio を乗算します。
canvas.width = W * dpr;
canvas.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ローソク足の数が多すぎて幅が狭くなる
最小幅を 0.6px に設定します。slotW < 1 の場合は、間隔の比率を小さくするか、データを集約します。
アニメーションの進行度を制御する
区間ごとのイージング関数を使用します。
- 冒頭の 3% でベースラインをすばやく確立
- 中間の 90% で一定速度で表示
- 終盤はドラマ性を高めるため、少し減速してもかまいません