mcpField
S
scroll-film

scroll-film

github.com/moh4696その他276 回インストール今日更新
リポジトリ

scroll-film について

scroll-film は、ページのスクロールで制御する固定キャンバス区間を作る Web アニメーションスキルです。レンダリング済みクリップをフレーム列またはシーク可能な動画に変換し、停止、スムージング、動きの軽減にも対応します。

主な機能

  1. 01スクロール進捗をフレームに対応
  2. 02WebP フレーム列または MP4 動画の生成
  3. 03クリップ接合部のジャンプ検査
  4. 04字幕用のフレーム停止
  5. 05動きの軽減設定への対応

活用シーン

  1. 01スクロール連動のランディングページアニメーション
  2. 02固定カメラ経路で生成したシーンの表示
  3. 03字幕表示中の映像停止
  4. 04スマートフォン表示用の別素材の準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/moh4696/open-3d-skills --skill scroll-film

インストール後、Claude Code で/scroll-filmを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
assets/
scroll-film.js
scripts/
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. ストーリーをビートに分けて書きます。 シーンごとに1つのビートを設定します。カメラが冒頭で何を映し、どこへ移動し、そこで訪問者が何を読むのかを決めます。ランディングページなら4〜7個のビートが適切です。ビート1つにつき、生成するクリップ1本(「レッグ」)を使います。
  2. まずキーフレームをすべて、同じスタイルで作成します。 レッグは開始フレームと終了フレームの間を生成します。あるレッグの終了フレームが次のレッグの開始フレームになります。動画を生成する前に、すべてのキーフレーム画像を同じモデルとスタイル参照で生成し、横に並べて確認します。画像ごとにスタイルがずれると、フィルム全体でもずれが生じます。
  3. 予算を明確に見積もります。 費用は、おおよそ静止画の費用に、レッグ数とクリップ1本あたりの価格を掛けた費用を加えたものです。スマートフォン向けに縦長バージョンを作ると、動画の費用は2倍になります(「スマートフォン」を参照)。本格的に取りかかる前に、静止画1枚とクリップ1本で調整してください(モデルの選択、価格、レート制限については motion-assets を参照)。

ビルド

  1. 手順2で用意した開始フレームと終了フレームを使って、レッグを生成します。つなぎ目をすべて確認してください。

    python3 scripts/prep.py seams legs/01.mp4 legs/02.mp4 legs/03.mp4
    

    4未満なら目立たず、10を超えるとジャンプが見えます。つなぎ目に問題があれば、前のレッグの正確な最終フレームを基準に、そのレッグを再生成します(静止画を再利用するのではなく、ffmpeg でフレームを抽出してください。モデルによって色や構図がわずかに変わるためです)。

  2. フレームと動画のどちらかを選びます。

    フレームシーケンス(デフォルト)スクラブ動画
    精度すべてのスクロール位置で正確なフレームを表示シーク位置が少し遅れることがあります。特に 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 4
    

    frames は、後続の各レッグの最初のフレームを除外します。これは前のレッグの最後のフレームと重複するためです。video は4フレームの GOP を使い、B フレームは使いません。GOP が250フレームの通常の書き出しでは、シークのたびに何百ものフレームをデコードするため、スクラブがカクつきます。

  3. セクションのマークアップを作成します。 高さによって、フィルムに必要なスクロール量が決まります。フィルム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>
    
  4. エンジンを接続します。

    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)を確認してください。キャプションが被写体に重ならないようにします。

コメント