HyperFrames
HTML はビデオの信頼できるソースです。コンポジションは、タイミングのための data-* 属性、アニメーションのための GSAP タイムライン、外観のための CSS を持つ HTML ファイルです。フレームワークは、クリップの可視性、メディア再生、タイムライン同期を処理します。
アプローチ
HTML を書く前に、高いレベルで考えます:
- What (何を) — 視聴者は何を体験すべきか? 物語の流れ、重要な瞬間、感情的なビートを特定します。
- Structure (構造) — いくつのコンポジションがあり、どのコンポジションがサブコンポジションかインラインか、どのトラックが何(ビデオ、オーディオ、オーバーレイ、キャプション)を運ぶか。
- Timing (タイミング) — どのクリップが継続時間を決めるか、トランジションはどこに配置されるか、ペーシングはどうか。
- Layout (レイアウト) — まず最終状態を構築します。以下の「アニメーションの前にレイアウト」を参照。
- Animate (アニメーション) — 次に、以下のルールを使用してモーションを追加します。
小さな編集(色の修正、タイミングの調整、1つの要素の追加)の場合は、直接ルールに進みます。
visual-style.md やアニメーションの指示がない場合は、house-style.md に従って、モーションのデフォルト、サイジング、カラーパレットを設定します。
アニメーションの前にレイアウト
すべての要素を、最も見える瞬間— 完全に入場し、正しく配置され、まだ退場していないフレーム — に配置します。これを静的な HTML + CSS として記述します。まだ GSAP はありません。
なぜこれが重要か: アニメーションの開始状態(画面外、0にスケーリング、不透明度0)に要素を配置し、それらを着地すべき場所にトゥイーンすると、最終的なレイアウトを推測することになります。重なりは、ビデオがレンダリングされるまで見えません。最初に最終状態を構築することで、モーションを追加する前にレイアウトの問題を確認して修正できます。
プロセス
- 各シーンの ヒーローフレーム を特定します — 最も多くの要素が同時に表示される瞬間。これが構築するレイアウトです。
- そのフレームの 静的な CSS を記述します。
.scene-contentコンテナは、width: 100%; height: 100%; padding: Npx;とdisplay: flex; flex-direction: column; gap: Npx; box-sizing: border-boxを使用して、シーン全体を埋める必要があります。パディングを使用してコンテンツを内側に押し込みます — コンテンツコンテナにposition: absolute; top: Npxを絶対に使用しないでください。絶対配置のコンテンツコンテナは、コンテンツが残りのスペースより高い場合にオーバーフローします。position: absoluteは装飾的な要素にのみ予約します。 gsap.from()で エントランスを追加 — 画面外/非表示 FROM から CSS の位置 TO にアニメーションします。CSS の位置が真実であり、トゥイーンはそこに到達するための経路を記述します。gsap.to()で エグジットを追加 — CSS の位置 FROM から画面外/非表示 TO にアニメーションします。
例
/* scene-content がシーンを埋め、パディングでコンテンツを配置 */
.scene-content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
padding: 120px 160px;
gap: 24px;
box-sizing: border-box;
}
.title {
font-size: 120px;
}
.subtitle {
font-size: 42px;
}
/* コンテナは任意のシーンサイズ(1920x1080、1080x1920など)にフィットします。
パディングでコンテンツを配置。Flex + gap でスペーシングを処理。 */
間違い — ハードコードされた寸法と絶対配置:
.scene-content {
position: absolute;
top: 200px;
left: 160px;
width: 1920px;
height: 1080px;
display: flex; /* ... */
}
// ステップ3: それらの位置にアニメーションイン
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
tl.from(".logo", { scale: 0.8, opacity: 0, duration: 0.4, ease: "power2.out" }, 0.3);
// ステップ4: それらの位置からアニメーションアウト
tl.to(".title", { y: -40, opacity: 0, duration: 0.4, ease: "power2.in" }, 3);
tl.to(".subtitle", { y: -30, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.1);
tl.to(".logo", { scale: 0.9, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.2);
要素が時間を共有する場合
要素 A が、要素 B が同じ領域に入場する前に退場する場合、両方ともそれぞれのヒーローフレームに対して正しい CSS 位置を持つ必要があります。タイムラインの順序により、それらが視覚的に共存することは決してないことが保証されます — しかし、レイアウトのステップをスキップすると、タイミングエラーによってそれらが誤って重なるケースを発見できません。
意図的な重なりと見なされるもの
レイヤードエフェクト(テキストの後ろのグロー、シャドウ要素、背景パターン)や z スタックデザイン(カードスタック、奥行きレイヤー)は意図的です。レイアウトのステップは、意図しない 重なり — 2つの見出しが互いの上に重なる、統計がラベルを覆う、コンテンツがフレームからはみ出す — を発見するためのものです。
データ属性
すべてのクリップ
| 属性 | 必須 | 値 |
|---|---|---|
id | はい | 一意の識別子 |
data-start | はい | 秒数またはクリップ ID 参照("el-1", "intro + 2") |
data-duration | img/div/コンポジションに必須 | 秒数。ビデオ/オーディオはメディアの長さがデフォルト。 |
data-track-index | はい | 整数。同じトラックのクリップは重なりません。 |
data-media-start | いいえ | ソースへのトリムオフセット(秒) |
data-volume | いいえ | 0-1(デフォルト 1) |
data-track-index は 視覚的なレイヤリングに影響しません — CSS の z-index を使用してください。
コンポジションクリップ
| 属性 | 必須 | 値 |
|---|---|---|
data-composition-id | はい | 一意のコンポジション ID |
data-start | はい | 開始時間(ルートコンポジション: "0" を使用) |
data-duration | はい | GSAP タイムラインの長さよりも優先されます |
data-width / data-height | はい | ピクセル寸法(1920x1080 または 1080x1920) |
data-composition-src | いいえ | 外部 HTML ファイルへのパス |
コンポジション構造
data-composition-src を介して読み込まれるサブコンポジションは、<template> ラッパーを使用します。スタンドアロンコンポジション(メインの index.html)は <template> を使用しません — data-composition-id の div を <body> に直接配置します。スタンドアロンファイルで <template> を使用すると、すべてのコンテンツがブラウザから隠され、レンダリングが壊れます。
サブコンポジション構造:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- コンテンツ -->
<style>
[data-composition-id="my-comp"] {
/* スコープ付きスタイル */
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// トゥイーン...
window.__timelines["my-comp"] = tl;
</script>
</div>
</template>
ルートで読み込み: <div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="10" data-track-index="1"></div>
ビデオとオーディオ
ビデオは muted playsinline である必要があります。オーディオは常に別の <audio> 要素です:
<video
id="el-v"
data-start="0"
data-duration="30"
data-track-index="0"
src="video.mp4"
muted
playsinline
></video>
<audio
id="el-a"
data-start="0"
data-duration="30"
data-track-index="2"
src="video.mp4"
data-volume="1"
></audio>
タイムライン契約
- すべてのタイムラインは
{ paused: true }で開始 — プレーヤーが再生を制御します - すべてのタイムラインを登録:
window.__timelines["<composition-id>"] = tl - フレームワークはサブタイムラインを自動的にネストします — 手動で追加しないでください
- 継続時間は GSAP タイムラインの長さではなく、
data-durationから取得されます - 継続時間を設定するために空のトゥイーンを作成しないでください
ルール(絶対条件)
決定論的: Math.random(), Date.now(), または時間ベースのロジックは禁止。擬似ランダム値が必要な場合(例: mulberry32)は、シード付き PRNG を使用してください。
GSAP: 視覚プロパティ(opacity, x, y, scale, rotation, color, backgroundColor, borderRadius, 変換)のみアニメーションします。visibility, display をアニメーションしたり、video.play() / audio.play() を呼び出したりしないでください。
アニメーションの競合: 同じ要素の同じプロパティを、複数のタイムラインから同時にアニメーションしないでください。
repeat: -1 は禁止: 無限リピートのタイムラインはキャプチャエンジンを壊します。コンポジションの継続時間から正確なリピート回数を計算してください: repeat: Math.ceil(duration / cycleDuration) - 1。
同期タイムライン構築: タイムラインを async/await, setTimeout, Promise の中では決して構築しないでください。キャプチャエンジンは、ページ読み込み後に同期的に window.__timelines を読み取ります。フォントはコンパイラによって埋め込まれるため、すぐに利用可能です — フォントの読み込みを待つ必要はありません。
絶対にしてはいけないこと:
window.__timelinesの登録を忘れること- オーディオにビデオを使用すること — 常にミュートされたビデオ + 別の
<audio> - ビデオをタイミング付き div の中に入れること — タイミングなしのラッパーを使用
data-layerを使用すること(data-track-indexを使用)またはdata-end(data-durationを使用)- ビデオ要素の寸法をアニメーションすること — ラッパー div をアニメーション
- メディアで play/pause/seek を呼び出すこと — フレームワークが再生を所有
data-composition-idなしでトップレベルのコンテナを作成すること- 任意のタイムラインまたはトゥイーンで
repeat: -1を使用すること — 常に有限のリピート - タイムラインを非同期に構築すること(
async,setTimeout,Promiseの中) - 後のシーンのクリップ要素に
gsap.set()を使用すること — ページ読み込み時に DOM に存在しません。代わりに、タイムライン内でクリップのdata-start時間以降にtl.set(selector, vars, timePosition)を使用してください。 - コンテンツテキストで
<br>を使用すること — 強制改行は実際にレンダリングされたフォント幅を考慮しません。自然に折り返すテキスト +<br>は、不要な余分な改行を生成し、重なりを引き起こします。代わりにmax-widthでテキストを折り返させてください。例外: 各単語が意図的に独自の行にある短い表示タイトル(例: "THE\nIMMORTAL\nGAME" の 130px)。
シーントランジション(絶対条件)
マルチシーンコンポジションは、これらすべてのルール に従わなければなりません。どれか一つでも違反すると、壊れたコンポジションになります。
- シーン間には必ずトランジションを使用します。 ジャンプカットは禁止。例外なし。
- すべてのシーンに必ずエントランスアニメーションを使用します。 すべての要素は
gsap.from()でアニメーション IN します。完全な形で表示される要素はありません。シーンに5つの要素がある場合、5つのエントランストゥイーンが必要です。 - 最終シーンを除き、エグジットアニメーションを使用しないでください。 つまり: トランジションが発動する前に、不透明度を0に、y を画面外に、スケールを0に、またはその他の「アウト」アニメーションを行う
gsap.to()は禁止です。トランジション自体がエグジットです。トランジションが開始する瞬間に、退場するシーンのコンテンツは 完全に表示されている 必要があります。 - 最終シーンのみ: 最後のシーンでは、要素をフェードアウトすることができます(例: フェードからブラックへ)。これのみが
gsap.to(..., { opacity: 0 })が許可される唯一のシーンです。
間違い — トランジションの前のエグジットアニメーション:
// 禁止 — トランジションが使用できる前にシーンを空にしてしまう
tl.to("#s1-title", { opacity: 0, y: -40, duration: 0.4 }, 6.5);
tl.to("#s1-subtitle", { opacity: 0, duration: 0.3 }, 6.7);
// トランジションは空のフレームで発動
正しい — エントランスのみ、トランジションがエグジットを処理:
// シーン1 エントランスアニメーション
tl.from("#s1-title", { y: 50, opacity: 0, duration: 0.7, ease: "power3.out" }, 0.3);
tl.from("#s1-subtitle", { y: 30, opacity: 0, duration: 0.5, ease: "power2.out" }, 0.6);
// エグジットトゥイーンなし — 7.2秒でのトランジションがシーン変更を処理
// シーン2 エントランスアニメーション
tl.from("#s2-heading", { x: -40, opacity: 0, duration: 0.6, ease: "expo.out" }, 8.0);
アニメーションガードレール
- 最初のアニメーションは 0.1-0.3 秒オフセット(t=0 ではない)
- エントランストゥイーン間でイージングを変える — シーンごとに少なくとも3つの異なるイージングを使用
- シーン内でエントランスパターンを繰り返さない
- 暗い背景で全画面の線形グラデーションを避ける(H.264 のバンディング — ラジアルまたはソリッド + ローカライズされたグローを使用)
- レンダリングされたビデオでは、見出しは 60px+、本文は 20px+、データラベルは 16px+
- 数値列には
font-variant-numeric: tabular-nums
visual-style.md やアニメーションの指示がない場合は、house-style.md に従って、美的デフォルトを設定します。
タイポグラフィとアセット
- フォント: CSS で必要な
font-familyを記述するだけ — コンパイラがサポートされるフォントを自動的に埋め込みます。フォントがサポートされていない場合、コンパイラが警告します。 - 外部メディアには
crossorigin="anonymous"を追加 - 動的なテキストオーバーフローには、
window.__hyperframes.fitTextFontSize(text, { maxWidth, fontFamily, fontWeight })を使用 - すべてのファイルは
index.htmlと共にプロジェクトルートにあります。サブコンポジションは../を使用
既存のコンポジションの編集
- 最初にコンポジション全体を読む — 既存のフォント、色、アニメーションパターンに合わせる
- リクエストされたものだけを変更する
- 関連のないクリップのタイミングを維持する
出力チェックリスト
-
npx hyperframes lintとnpx hyperframes validateの両方が通過している - コントラスト警告が対処されている(以下の品質チェックを参照)
- アニメーションの振り付けが確認されている(以下の品質チェックを参照)
品質チェック
コントラスト
hyperframes validate はデフォルトで WCAG コントラスト監査を実行します。5つのタイムスタンプを取得し、ページをスクリーンショットし、すべてのテキスト要素の背後にある背景ピクセルをサンプリングし、コントラスト比を計算します。失敗は警告として表示されます:
⚠ WCAG AA コントラスト警告 (3):
· .subtitle "secondary text" — 2.67:1 (必要 4.5:1, t=5.3s)
警告が表示された場合:
- 暗い背景の場合: 4.5:1(通常テキスト)または 3:1(大きいテキスト、24px+ または 19px+ 太字)をクリアするまで、問題のある色を明るくする
- 明るい背景の場合: 暗くする
- パレットファミリー内に留まる — 新しい色を発明せず、既存の色を調整する
- クリーンになるまで
hyperframes validateを再実行する
--no-contrast を使用すると、迅速な反復中はスキップし、後で確認できます。
アニゲーションマップ
アニメーションを作成した後、アニメーションマップを実行して振り付けを確認します:
...(続く)