scroll-film について
scroll-film は、ページのスクロールで制御する固定キャンバス区間を作る Web アニメーションスキルです。レンダリング済みクリップをフレーム列またはシーク可能な動画に変換し、停止、スムージング、動きの軽減にも対応します。
主な機能
- 01スクロール進捗をフレームに対応
- 02WebP フレーム列または MP4 動画の生成
- 03クリップ接合部のジャンプ検査
- 04字幕用のフレーム停止
- 05動きの軽減設定への対応
活用シーン
- 01スクロール連動のランディングページアニメーション
- 02固定カメラ経路で生成したシーンの表示
- 03字幕表示中の映像停止
- 04スマートフォン表示用の別素材の準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ moh4696/ open-3d-skills --skill scroll-filmインストール後、Claude Code で/scroll-filmを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlassets/scroll-film.jsscripts/prep.py詳細
スクロールフィルム
スクロールフィルムは、縦に長いセクションを訪問者がスクロールする間、キャンバスを固定し、スクロール位置に応じてフレームを選びます。訪問者がカメラを動かすため、すべてのフレームが事前にレンダリングされていても、立体的に感じられます。動きがリアルタイム 3D では表現しきれないほど豊かで(生成された世界、液体、人など)、カメラの経路が固定されている場合に最適なツールです。
ツール
| ファイル | 機能 |
|---|---|
assets/scroll-film.js | エンジン:固定表示されるキャンバス、停止区間を含む進行度からフレームへのキー設定、フレームシーケンスまたは動画、スムージング、動きを減らす設定 |
scripts/prep.py frames | クリップを1つの番号付き WebP シーケンスとマニフェストに結合します |
scripts/prep.py video | クリップをシークに最適化してエンコードした1つの MP4 に結合します |
scripts/prep.py seams | クリップ間のすべてのつなぎ目を評価し、目に見えるジャンプがある場合は失敗します |
ffmpeg と ffprobe、Python 3.9+、および pip install pillow numpy が必要です。クリップの作成には motion-assets、透明な被写体には alpha-cutout、結果の確認には site-check と frame-capture を使います。
生成を始める前に計画する
- ストーリーをビートに分けて書きます。 シーンごとに1つのビートを設定します。カメラが冒頭で何を映し、どこへ移動し、そこで訪問者が何を読むのかを決めます。ランディングページなら4〜7個のビートが適切です。ビート1つにつき、生成するクリップ1本(「レッグ」)を使います。
- まずキーフレームをすべて、同じスタイルで作成します。 レッグは開始フレームと終了フレームの間を生成します。あるレッグの終了フレームが次のレッグの開始フレームになります。動画を生成する前に、すべてのキーフレーム画像を同じモデルとスタイル参照で生成し、横に並べて確認します。画像ごとにスタイルがずれると、フィルム全体でもずれが生じます。
- 予算を明確に見積もります。 費用は、おおよそ静止画の費用に、レッグ数とクリップ1本あたりの価格を掛けた費用を加えたものです。スマートフォン向けに縦長バージョンを作ると、動画の費用は2倍になります(「スマートフォン」を参照)。本格的に取りかかる前に、静止画1枚とクリップ1本で調整してください(モデルの選択、価格、レート制限については
motion-assetsを参照)。
ビルド
-
手順2で用意した開始フレームと終了フレームを使って、レッグを生成します。つなぎ目をすべて確認してください。
python3 scripts/prep.py seams legs/01.mp4 legs/02.mp4 legs/03.mp44未満なら目立たず、10を超えるとジャンプが見えます。つなぎ目に問題があれば、前のレッグの正確な最終フレームを基準に、そのレッグを再生成します(静止画を再利用するのではなく、ffmpeg でフレームを抽出してください。モデルによって色や構図がわずかに変わるためです)。
-
フレームと動画のどちらかを選びます。
フレームシーケンス(デフォルト) スクラブ動画 精度 すべてのスクロール位置で正確なフレームを表示 シーク位置が少し遅れることがあります。特に Safari や性能の低いスマートフォンで顕著です サイズ 1280〜1600 px で1フレームあたり20〜60 kB その約3分の1 向いている用途 およそ300フレームまで サイズを精度より重視する長いフィルム python3 scripts/prep.py frames legs/*.mp4 site/film/lg --width 1600 --fps 24 python3 scripts/prep.py frames legs/*.mp4 site/film/sm --width 900 --fps 24 --quality 72 python3 scripts/prep.py video legs/*.mp4 site/film.mp4 --width 1920 --gop 4framesは、後続の各レッグの最初のフレームを除外します。これは前のレッグの最後のフレームと重複するためです。videoは4フレームの GOP を使い、B フレームは使いません。GOP が250フレームの通常の書き出しでは、シークのたびに何百ものフレームをデコードするため、スクラブがカクつきます。 -
セクションのマークアップを作成します。 高さによって、フィルムに必要なスクロール量が決まります。フィルム1秒あたりおよそ1画面分スクロールする設定にすると意図的な動きに感じられ、それより短いと急いでいるように感じられます。
<section id="film" style="height: 600vh"> <div class="pin" style="position: sticky; top: 0; height: 100vh"> <canvas style="width: 100%; height: 100%; display: block"></canvas> <div class="captions">...</div> </div> </section> -
エンジンを接続します。
import { ScrollFilm } from "./scroll-film.js"; const small = matchMedia("(max-width: 760px)").matches; const film = new ScrollFilm({ section: document.querySelector("#film"), canvas: document.querySelector("#film canvas"), frames: { count: 247, src: (i) => `film/${small ? "sm" : "lg"}/${String(i).padStart(3, "0")}.webp` }, keys: [[0, 0], [0.18, 60], [0.3, 60], [0.62, 160], [0.72, 160], [1, 246]], onUpdate: ({ progress }) => captions(progress), onReady: () => document.body.classList.add("film-ready"), });keysは[progress, frame]のペアです。同じフレームを指定するキーを2つ設定すると、停止区間になります。フィルムがビートで一時停止している間に、キャプションがスクロールして表示されたり消えたりします。言葉を表示する各ビートに停止区間を設定してください。停止区間がないと、動いている映像にテキストが重なり、誰も読みません。smooth(デフォルトは0.18)は、フレームレートに依存せず、スクロール位置に合わせてフィルムを滑らかに動かします。そのため、マウスホイールの段階的なスクロールがジャンプではなく動きになります。ページのスクロールがすでにスムージングされている場合(Lenis など)は、smooth: 1を設定してください。キャプションとレイヤーは
onUpdateでprogressを読み取ります。停止区間の始まりの短い範囲でキャプションをフェードインし、終わりでフェードアウトさせます。必要に応じて背後に淡いスクリーンを敷き、フレーム内で最も動きの激しい部分を避けて配置してください。
スマートフォン
16:9 の映像を中央基準で縦長のスマートフォン向けにクロップすると、通常は被写体が見切れてしまいます。すべてのビートでクロップを確認してください。うまくいかない場合は、縦向きのキーフレームから縦向き(9:16)の脚を生成し、メディアクエリで切り替える別セットとして用意してください。その際、動画の予算が倍になることを事前に伝えてください。中央基準のクロップが許容されるのは、すべてのビートで被写体が中央の 3 分の 1 に収まる場合のみです。
スマートフォンでは小さいフレームセットも使用し、映像全体のサイズを約 8 MB 未満に抑えてください。
読み込み
- 最初の描画で空のボックスではなく映像が表示されるよう、フレーム 0 を HTML 内の
<img>(またはキャンバスの背景)として配置してください。 - エンジンはフレーム 0、次に最後のフレームを読み込み、その後、粗いものから細かいものへと間隔を狭めながら(64 枚ごと、32 枚ごと、というように)、一度に 6 枚ずつ読み込みます。訪問者が高速でスクロールしても、常にデコード済みフレームの近くに移動し、映像が鮮明になっていきます。
- 動画モードでは、再生前にファイル全体を blob に取得するため、シーク時にネットワーク待ちが発生しません。
- Range リクエストに応答するホストから配信してください。
python3 -m http.serverは対応していないため、そこで配信する動画はシークできません。ローカルでは、frame-captureスキルの serve.py を使用してください。
モーションの低減
prefers-reduced-motion: reduce の場合、エンジンは再生する代わりにキーフレーム間を移動するため(デフォルトでは reducedMotion: "steps")、すべてのビートで映像が表示されます。"still" では最後のキーフレームのみが表示されます。
確認
frame-captureスキルを使い、低いフレームレートでスクロールを録画して、時間(秒)単位でペースを確認してください。record.py URL check.mp4 --fps 8 --seconds 20 --scroll "0:0 18:100%"を実行します。- 上下に高速でシークしてください。何もない画面にならないこと、継ぎ目でちらつかないこと、最後にピン留めがきれいに解除されることを確認します。
- 帯域制限をかけた接続で読み込んでください。最初の描画でフレーム 0 が表示されることを確認します。
- スマートフォンのビューポートと、高さの低いノートパソコンのビューポート(1280x720)を確認してください。キャプションが被写体に重ならないようにします。