Remotion AbsoluteFill とレイアウトシステム完全ガイド
Remotion AbsoluteFill とレイアウトシステム完全ガイド
Remotionでコンポーネントを作り始めると、最初のうちは「なぜ <AbsoluteFill> をこんなに頻繁に使うのか」が腑に落ちにくいことがあります。慣れてくると、これがRemotionのレイアウト設計の中心にある部品であり、理解が深まれば多層コンポジション・マルチフォーマット出力・レスポンシブなサイジングのすべてが自然に見えてくることがわかります。
本記事では <AbsoluteFill> の仕組みと動作原理を整理したうえで、実際のプロダクションで使われるレイアウトパターンをコードつきで紹介します。スプリットスクリーン・コーナーバッジ・テキストオーバーレイ・マルチフォーマット同時出力まで、一通りの引き出しを揃えることを目指します。
<AbsoluteFill> は何をしているのか
<AbsoluteFill> は "remotion" からインポートするコンポーネントで、実体は次のスタイルを持つ <div> です。
position: "absolute"
top: 0
left: 0
right: 0
bottom: 0
display: "flex"
flexDirection: "column"
以上です。シンプルに見えますが、この4点の絶対配置とゼロインセットの組み合わせが決定的です。Remotionのレンダリングルートは position: "relative" でコンポジションの width × height にぴったり収まるコンテナを生成します。その中に配置された <AbsoluteFill> はすべて同じ矩形(キャンバス全体)を埋めます。
これがレイヤー合成を簡単にする理由です。兄弟要素として並べた複数の <AbsoluteFill> は自動的に重なります。幅・高さ・絶対座標の計算は不要です。DOM上で後に来る要素が視覚的に上に重なります。
import { AbsoluteFill } from "remotion";
export const LayeredComp: React.FC = () => (
<AbsoluteFill style={{ backgroundColor: "#000" }}>
{/* 背景レイヤー */}
<AbsoluteFill>
<BackgroundVideo />
</AbsoluteFill>
{/* 中間レイヤー */}
<AbsoluteFill>
<AnimatedSubject />
</AbsoluteFill>
{/* 前景・UIレイヤー */}
<AbsoluteFill>
<LowerThird />
</AbsoluteFill>
</AbsoluteFill>
);
style プロップで動作を追加する
<AbsoluteFill> の絶対配置スタイルは固定されていますが、style プロップで追加のCSSを重ねることができます。
// 中央揃えレイアウト
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<h1>中央のテキスト</h1>
</AbsoluteFill>
// 半透明オーバーレイ
<AbsoluteFill style={{ backgroundColor: "rgba(0,0,0,0.45)" }} />
// グラデーションベール
<AbsoluteFill
style={{
background: "linear-gradient(to top, rgba(0,0,0,0.8) 0%, transparent 50%)",
}}
/>
注意点として、<AbsoluteFill> にすでに position: "absolute" が設定されているため style プロップに left や top を渡しても機能しますが、「キャンバス全体を埋める」という契約が崩れます。キャンバスより小さい配置済み要素には、<AbsoluteFill> ではなく通常の <div style={{ position: "absolute" }}> を使うのが適切です。
useVideoConfig() でレスポンシブなサイジングを実現する
コンポーネントの中にピクセル値をハードコードするのはRemotionでのアンチパターンです。width: 1920 と書いた瞬間に、そのコンポーネントは16:9専用になります。代わりに useVideoConfig() を使います。
import { AbsoluteFill, useVideoConfig } from "remotion";
export const ResponsiveBackground: React.FC = () => {
const { width, height, fps, durationInFrames } = useVideoConfig();
return (
<AbsoluteFill>
<svg width={width} height={height}>
<rect width={width} height={height} fill="#1a1a2e" />
<line
x1={0}
y1={height * 0.6}
x2={width}
y2={height * 0.4}
stroke="#ffffff"
strokeWidth={2}
strokeOpacity={0.08}
/>
</svg>
</AbsoluteFill>
);
};
width・height・fps・durationInFrames はすべて <Composition> の定義から来ます。コンポジションを9:16に変更すると、useVideoConfig() は即座に新しい寸法を返します。コンポーネントの変更は不要です。
z-indexとレイヤー順の管理
デフォルトではDOM順(後にある要素が上)でレイヤーが重なります。z-index で上書きできます。
<AbsoluteFill>
{/* DOMで最初だが、z-indexで一番上に */}
<AbsoluteFill style={{ zIndex: 10 }}>
<UrgentOverlay />
</AbsoluteFill>
<AbsoluteFill style={{ zIndex: 1 }}>
<ContentLayer />
</AbsoluteFill>
</AbsoluteFill>
実務上はz-indexの管理が複雑になりがちです。背景→中間→前景の順にDOMを並べ、z-indexオーバーライドを使わないのが長期的に保守しやすいパターンです。
よく使うレイアウトパターン
パターン1:背景+テキストオーバーレイ(基本構造)
映像制作で最も頻出するレイアウトです。
import { AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate } from "remotion";
export const TextOverVideo: React.FC = () => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill>
{/* レイヤー1:背景 */}
<AbsoluteFill style={{ backgroundColor: "#0d0d18" }}>
<BackgroundClip />
</AbsoluteFill>
{/* レイヤー2:グラデーションビネット */}
<AbsoluteFill
style={{
background: "radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.65) 100%)",
}}
/>
{/* レイヤー3:タイトル */}
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
opacity,
}}
>
<h1
style={{
fontFamily: `"Yu Gothic", "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif`,
fontSize: height * 0.08,
color: "#fff",
margin: 0,
textAlign: "center",
}}
>
メインタイトル
</h1>
</AbsoluteFill>
</AbsoluteFill>
);
};
パターン2:スプリットスクリーン
import { AbsoluteFill, useVideoConfig } from "remotion";
export const SplitScreen: React.FC = () => {
const { width, height } = useVideoConfig();
return (
<AbsoluteFill style={{ flexDirection: "row" }}>
<div
style={{
width: width / 2,
height,
backgroundColor: "#1a1a2e",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<LeftContent />
</div>
<div
style={{
width: width / 2,
height,
backgroundColor: "#16213e",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<RightContent />
</div>
</AbsoluteFill>
);
};
スプリットスクリーンでは最上位の <AbsoluteFill> に flexDirection: "row" を指定します(デフォルトは "column")。
パターン3:コーナーバッジ / 透かし
ウォーターマークはすべてのコンテンツより上に表示される必要がありますが、レイアウトには干渉しません。
export const WithWatermark: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { width, height } = useVideoConfig();
return (
<AbsoluteFill>
<AbsoluteFill>{children}</AbsoluteFill>
{/* コーナーバッジ — AbsoluteFillではなく通常のdiv */}
<div
style={{
position: "absolute",
top: height * 0.03,
right: width * 0.025,
backgroundColor: "rgba(255,255,255,0.12)",
backdropFilter: "blur(8px)",
borderRadius: 8,
padding: "8px 16px",
fontFamily: `-apple-system, "Segoe UI", Roboto, sans-serif`,
fontSize: height * 0.02,
color: "#fff",
}}
>
LIVE
</div>
</AbsoluteFill>
);
};
<AbsoluteFill> ではなく position: "absolute" の通常の <div> を使うことで、キャンバスを埋めることなく正確な位置に配置できます。サイズは height * 0.02 のようにビデオ寸法に対する比率で指定すると、フォーマット変更時にも自動でスケールします。
パターン4:三分割法グリッドのコンテンツ配置
export const RuleOfThirds: React.FC = () => {
const { width, height } = useVideoConfig();
return (
<AbsoluteFill>
<div
style={{ position: "absolute", top: height * 0.08, left: width * 0.06, right: width * 0.06 }}
>
<Headline />
</div>
<div
style={{
position: "absolute",
top: height * 0.33,
left: width * 0.1,
right: width * 0.1,
height: height * 0.34,
}}
>
<MainGraphic />
</div>
<div
style={{ position: "absolute", bottom: height * 0.08, left: width * 0.06, right: width * 0.06 }}
>
<LowerThirdBar />
</div>
</AbsoluteFill>
);
};
1つのコンポーネントからマルチフォーマット出力
useVideoConfig() を徹底的に使えば、1つのコンポーネント定義から16:9・9:16・1:1の3フォーマットを同時出力できます。
// Root.tsx
import { Composition } from "remotion";
import { UniversalCard } from "./UniversalCard";
export const RemotionRoot: React.FC = () => (
<>
<Composition
id="Card-16x9"
component={UniversalCard}
width={1920}
height={1080}
fps={30}
durationInFrames={180}
defaultProps={{ title: "サンプル", subtitle: "サブタイトル" }}
/>
<Composition
id="Card-9x16"
component={UniversalCard}
width={1080}
height={1920}
fps={30}
durationInFrames={180}
defaultProps={{ title: "サンプル", subtitle: "サブタイトル" }}
/>
<Composition
id="Card-1x1"
component={UniversalCard}
width={1080}
height={1080}
fps={30}
durationInFrames={180}
defaultProps={{ title: "サンプル", subtitle: "サブタイトル" }}
/>
</>
);
UniversalCard 内部で useVideoConfig() を使ってすべてのサイズを比率で指定していれば、コンポーネントを一切変更せずに3フォーマットのレンダリングが可能です。
センタリングの落とし穴
<AbsoluteFill> のデフォルトは flexDirection: "column" です。flexDirection: "column" の場合:
justifyContentは垂直方向を制御しますalignItemsは水平方向を制御します
これは多くの開発者の直感と逆です。要素が水平中央に寄るが上端に張り付いている場合、justifyContent: "center" が抜けているのが原因です。両軸中央にするには両方が必要です。
// 正しい両軸中央揃え
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<MyElement />
</AbsoluteFill>
ウィプトランジション(スライドイン)
overflow: "hidden" と子要素のtranslateを組み合わせたウィプアニメーションは、放送系の映像制作で定番の手法です。
import { AbsoluteFill, useCurrentFrame, interpolate } from "remotion";
import { spring } from "remotion";
export const WipeReveal: React.FC = () => {
const frame = useCurrentFrame();
const progress = spring({
frame,
fps: 30,
config: { damping: 14, stiffness: 80, mass: 1 },
});
const translateX = interpolate(progress, [0, 1], [-1920, 0]);
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<AbsoluteFill
style={{
transform: `translateX(${translateX}px)`,
backgroundColor: "#1a1a2e",
}}
>
<ContentHere />
</AbsoluteFill>
</AbsoluteFill>
);
};
外側の <AbsoluteFill> がクリップ境界を定義し、内側がスライドします。
初心者が陥りやすい5つのミス
ミス1:position指定のない親要素の中にAbsoluteFillを置く
<AbsoluteFill> は position: "absolute" なので、最寄りの position: "relative" な祖先に対して配置されます。親 <div> に位置指定がないとビューポートを基準にエスケープします。
対策:親divに position: "relative" を指定するか、ルートレベルでのみ <AbsoluteFill> を使います。
ミス2:ピクセル値のハードコード
width: 1920 と書いたコンポーネントは事実上16:9専用です。
対策:useVideoConfig() から width と height を取得し、フォントや余白は height * 0.05 のように比率で指定します。
ミス3:すべてのレイヤーに背景色を設定する 下のレイヤーの内容がすべて隠れてしまいます。 対策:背景色は最下層の1つだけに設定します。
ミス4:意図せずoverflow: hiddenを設定する アニメーションのリビール表現のために意図的に使う場合は問題ありませんが、無意識に設定するとコンテンツが見切れます。
ミス5:CSSトランジションを使う(代わりにuseCurrentFrameを使う)
CSSの transition プロパティはRemotionの時間軸と連動しないため、レンダリング時に不正確になる場合があります。アニメーションは常に useCurrentFrame() ベースで実装します。
RenderCompのレイアウトテンプレートで設計を省力化
レイアウト構造を一から設計するのは、特に複数フォーマット対応が必要な場合に手間がかかります。RenderComp(rendercomp.com)では、レイヤー構造・フォーマット対応・センタリング・ウィプトランジションをすべて実装済みのRemotionテンプレートをライブラリとして提供しています。レイアウトの土台づくりに時間を使わず、コンテンツに集中できる環境を用意しました。
よくある質問
Q1:AbsoluteFillの中でCSS Gridは使えますか?
はい。AbsoluteFillをキャンバス全体を埋めるコンテナとして使い、その内側の通常の <div> に display: "grid" を適用することで細かなグリッドレイアウトが実現できます。
Q2:AbsoluteFillに必須のpropsはありますか?
ありません。style プロップはオプションです。何も渡さない場合、position: "absolute", top: 0, left: 0, right: 0, bottom: 0, display: "flex", flexDirection: "column" が適用された <div> としてレンダリングされます。
Q3:レイヤーを半透明にするにはどうすればよいですか?
style プロップで opacity: 0.5 を渡すか、backgroundColor: "rgba(0,0,0,0.4)" のような透明な背景色を設定します。両方ともレンダリング時に正しく合成されます。
Q4:AbsoluteFillの位置をアニメーションさせることはできますか?
はい。style プロップで transform: "translateX(${x}px)" を使います。useCurrentFrame() で計算した値を渡します。top・left・right・bottom を直接アニメーションさせるのは非推奨です。CSSトランスフォームはGPU合成に適しています。
Q5:9:16(縦型)コンテンツのセーフゾーンはどう確保しますか?
useVideoConfig() で height を取得し、上下に余白を設けます:paddingTop: height * 0.12, paddingBottom: height * 0.12。これにより、SNSプラットフォームのUI(ストーリーのプログレスバー・ハンドル名表示など)に重要なコンテンツが隠れません。
Q6:borderRadiusでコンテンツがクリップされません。なぜですか?
borderRadius だけではクリップは発生しません。overflow: "hidden" を一緒に指定する必要があります。これはCSSの標準動作です。
Q7:AbsoluteFillとposition: absoluteのdivは何が違いますか?
出力HTMLは実質同じです。<AbsoluteFill> は意図(このコンポーネントはキャンバス全体を埋める)を明示するコンポーネントであり、flexカラムレイアウトが最初から設定されています。どちらを使うかはコードの可読性の好みによります。