mcpField
W
whiteboard-animation

whiteboard-animation

github.com/iart-aiクリエイティブ・メディア601.5k 回インストール18 週間前に更新
リポジトリ

whiteboard-animation について

whiteboard-animationは、SVGパスから手描き風のホワイトボードアニメーションを作るスキルです。線の描画、ペン先に追従する手、ナレーションに沿った構成を扱い、単体HTMLやRemotion動画として出力できます。

主な機能

  1. 01SVGパスを一筆ずつ描画
  2. 02描画の先端にマーカーハンドが追従
  3. 03ナレーション順のシーン構成
  4. 04手書きアニメーションと画面消去の切り替え
  5. 05単体HTMLとRemotion動画への出力

活用シーン

  1. 01手描き風の解説動画制作
  2. 02スケッチやロゴの描画アニメーション
  3. 03手書きラベルとナレーションの同期

インストール

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

$npx skills add https://github.com/iart-ai/explainer-video-skills --skill whiteboard-animation

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

同梱ファイル

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

README.md
references/
draw-on-recipes.md
whiteboard-pipeline.md

詳細

ホワイトボードアニメーション

VideoScribe / Doodly のような演出です。手がボードにイラストや文字を一筆ずつ描き、ナレーションが内容を説明します。自動で描かれるアニメーション SVG パスと、描画の先端を追うマーカーハンドを組み合わせ、ナレーションの順序に合わせて要素を並べることで、ボードを埋めていきます。

使用する場面

  • 手がホワイトボードに絵や文字を描く、「スクリブ」/落書き風の解説動画。
  • スケッチやロゴが、線ごとにリアルタイムで描かれるアニメーション。
  • ナレーションに合わせて自動で書かれる文字。
  • 静止画を、順を追って一筆ずつ表示する演出に変える場合。

基本の仕組み:SVG のストローク描画

パスの全長から 0 まで stroke-dashoffset をアニメーションさせると、パスが「自分で描かれる」ように見えます。隙間が縮まり、ストロークが始点から終点に向かって現れます。

const path = document.querySelector("#stroke");
const len  = path.getTotalLength();
path.style.strokeDasharray  = len;       // one dash as long as the whole path
path.style.strokeDashoffset = len;       // pushed fully out of view = invisible
path.getBoundingClientRect();            // force reflow before transitioning
path.style.transition       = "stroke-dashoffset 1.2s linear";
path.style.strokeDashoffset = "0";       // draws start → end

<path> に pathLength="1" を指定して正規化します。これにより、実際の長さに関係なく、stroke-dasharray:1; stroke-dashoffset:1 でどのパスも描画でき、イージングを適用した単一の値(0→1)が描画の進行度になります。一定の速度で手が動いて見えるのは linear(またはごく緩やかなイージング)です。長いストロークに ease-in-out を使うとマーカーの動きがぎこちなくなるため、避けてください。

マーカーハンドが先端を追う

この演出の要となるのは、描画中の正確な位置に手を置くことです。現在の描画進行度におけるパス上の点を取得し、毎フレームそこに手の画像を配置します。手のアセットは、ペン先がパス上の点に合うように配置してください(画像の角ではなく、ペン先が点にくるようオフセットします)。

const hand = document.querySelector("#hand");   // <img>/<g>, tip at its top-left "nib"
const NIB = { x: 6, y: 4 };                      // nib offset inside the hand art
function drawOn(path, hand, dur = 1200) {
  const len = path.getTotalLength(), t0 = performance.now();
  path.style.strokeDasharray = len;
  (function frame(now) {
    const k = Math.min(1, (now - t0) / dur);     // 0→1 progress
    path.style.strokeDashoffset = len * (1 - k); // expose start→tip
    const p = path.getPointAtLength(len * k);    // current tip in SVG coords
    hand.setAttribute("transform", `translate(${p.x - NIB.x} ${p.y - NIB.y})`);
    if (k < 1) requestAnimationFrame(frame);
  })(t0);
}

getPointAtLength(len * k) が要となります。これは strokeDashoffset を動かす進行度と同じ位置にある、パス上の座標を返すため、ペン先は伸びていくストロークにぴったり沿います。別のストロークに移る間は手を持ち上げます(フェードアウトして次の始点へ移動)。手が何もないボードの上を滑るように見えるのを防げます。

表示順序 — ボードを埋めていく

人が場面を描く順序に合わせて、ナレーションの順に要素を並べます。イラストや文字のまとまりを描いて少し止めてから、次を描き始めます。1 つの絵の中ではストロークをずらして描画し、各要素が描き終わったら少し間を置いてから手を移動します。1 つの絵につき、ナレーションの 1 拍を割り当てます。同時に 2 つの要素を描かないでください。視聴者の目も、1 つだけの手も、一度に追える先端は 1 つです。

手順仕組み
ストロークを描くstroke-dashoffset を len→0 にし、手を getPointAtLength(len*k) に配置
複数ストロークの絵ストロークをずらして描画し、手を次のストロークの始点へ移動
手書き文字読み進める順に文字や単語のストロークを描き、手で追う
停止要素の描画後、手をボードから外して一時停止(0.4〜1.0s)
次の要素手を次の始点へ移動またはフェードし、描画を開始
消去/クリア次のセクションに備えて、ボードをワイプまたは逆方向に描画して消去

絵を描画可能なパスに変換する

描画アニメーションには、描画順に並んだ、塗りつぶしのない単一ストロークのパスが必要です。複合塗りつぶし図形は使えません。

  • 線画は、塗りではなく開いたストロークとして作成またはトレースします(Illustrator/Inkscape では、塗りではなくアウトラインを使います)。塗りつぶされた図形にはアニメーションする「ストローク」がありません。塗りを中心線のストロークに変換するか、アウトラインを描き終えてから塗りを適用してください。
  • 順序が重要です。<path> 要素は、手で描く順に SVG 内に並べ、ドキュメントの順序に沿ってアニメーションさせます。
  • 長く途切れない線が最も自然に見えます。実際にペンを離す箇所だけで分割してください。
  • ベタ塗りの領域では、先にアウトラインを描き、その後ろで塗りをフェードまたは拡大表示します(opacity またはクリップによる表示)。こうすると「後から色を塗った」ように見えます。
  • 手書き文字には、ストローク体/手書きフォントを使ってグリフをパスに変換し、各グリフのパスをストロークの順に描画します。または、単語をマスクし、手をマスクの端に沿わせながら左から右へ表示する方法でも表現できます。

マーカーハンドのアセット

ペン先の位置が分かる、マーカーを持つ手の PNG/SVG を用意します。ペン先のオフセットを一度測定し(上記の NIB)、繰り返し使用します。一貫性を保つため、作品全体で同じ手のアセットを使います。左手が必要な場合に限り、反転(scaleX(-1))してください。奥行きを出すため、手の下に薄い影を加えます。手を「退場」させる停止中は、影を消します。

消去/ワイプのトランジション

セクションの切り替わりでは、ボードがごちゃつかないように消去します。

  • ワイプ:塗りつぶされたグループ上をclip-path(または白い長方形)でアニメーションさせて消去し、手で消す動きを加えることもできます。
  • 逆順の描画:strokeDashoffsetを0→lenにして線を描画前の状態に戻し、手も後ろ向きに動かします。
  • フェード+再描画:前のグループをフェードアウトしながら、次のグループの描画を開始します。

トランジションに意図が伝わるよう、ワイプは常に左から右へ、といった一貫したルールを決めます。

ナレーションに合わせたペース配分

描画時間は固定タイマーではなく、話されるセリフに合わせます。ナレーションの速度を約2.3語/秒として見積もり、各描画の合計時間を、対応するセリフの長さに短い表示時間を加えた程度に設定します。ナレーションで読み上げるラベルは、その単語が話されるタイミングで描画が完了するようにします。強調するセリフでは手の動きを遅くし、細かなつなぎの線は速く描きます。ナレーションが続いているのに、手が描画済みのボード上で止まったままにならないようにします。手を画面外へ動かすか、次の要素の描画を始めます。

出力チェックリスト

  • すべての線は、描画順に沿った単一の開いたパスです。完成形が突然表示されることはありません。
  • 各ストロークの描画中、手のペン先は常に描画の先端とぴったり一致します(浮いたり、遅れたりしません)。
  • 個別のストロークや要素の間では手をきちんと持ち上げます。何もないボード上を滑らせません。
  • 要素はナレーションの順番に従って一つずつ描画し、次へ進む前にそれぞれ表示状態を保ちます。
  • 手書き文字は読み順に描画し、塗りつぶしは輪郭の後に表示します。
  • 消去/ワイプのルールに一貫性を持たせ、ボードが過度にごちゃつかないようにします。
  • 描画タイミングは、対応するナレーションのセリフに合わせます。
  • prefers-reduced-motion:描画アニメーションを使わず、完成したボードを表示します(すべての線をoffset 0にし、手は非表示にします)。

配信と検証(スタンドアロン HTML)

同梱ヘルパー(scripts/):scripts/seek-shot.sh anim.html 0 1.5 3 は ?t=N ハーネスで各タイミングを静止させてスクリーンショットを撮影します。scripts/contact-sheet.sh sheet.png frame-*.png はそれらを一覧できるコンタクトシートにまとめます。scripts/README.md を参照してください。

自己完結型のホワイトボードシーン(描画アニメーションのイラスト、手書き、短い書き込み演出)では、成果物はブラウザーで直接開ける HTML ファイル 1 つです。ビルド手順もレンダリングパイプラインも必要ありません。Web モーションには単一ファイルが適切です。1 ファイルで済むなら、バンドラーを使う必要はありません。(ナレーションを組み込んだ本格的なスクリブ動画の場合は、Remotion のコンポジションとして構築し、remotion still で検証してください。explainer-video を参照してください。)

出力仕様:

  • .html ファイル 1 つ:SVG をインラインで記述(描画順にパスを並べ、pathLength="1" または測定済みの長さを指定)、手のアセットをインラインまたはデータ URI で含め、描画用ドライバーをインラインの <script> に記述します。
  • 描画全体を1 つのマスタータイムライン(GSAP の tl または 1 つの rAF クロック)で制御し、シーケンス全体を単一の再生ヘッドでシークできるようにします。strokeDashoffset と手の位置は、どちらも同じ進行値で制御します。
  • 下記のシーク用ハーネスを含め、任意のタイミングを静止させてスクリーンショットを撮れるようにします。t で静止すると、パスがどこまで描画されたかを正確に確認できます。検証ではこれを基準にします。

シーク用ハーネス — スクリーンショット用に正確なタイミングで静止します。 ?t=N はマスタータイムラインを N 秒にシークして一時停止するため、描画途中のフレームでも、スクリーンショットに静止した再現可能なフレームが収まります。

<script>
  // ... build your master timeline as `tl` (drawOn calls placed on it) ...
  const t = new URLSearchParams(location.search).get("t");
  if (t !== null) { tl.pause(); tl.seek(parseFloat(t)); }  // frozen at t seconds
  // no ?t → plays normally
  window.__ready = true;            // ready signal for headless wait
  console.log("duration", tl.duration());
</script>

検証ループ — レンダリング → 静止 → スクリーンショット → 確認:

  1. タイムライン上の複数のタイミング(開始、中間の描画フレーム、終了)でファイルを開きます: …/scribe.html?t=0、?t=<dur/2>、?t=<dur>。終了時刻はコンソールで tl.duration() を確認してください。少なくとも 1 つはストロークの途中の t を選びます。ここで、ペン先が描画の先端に接するように見えるかどうかが決まります。
  2. 静止した各フレームのヘッドレススクリーンショットを撮影します:
    npx playwright screenshot --wait-for-timeout=500 "file://$PWD/scribe.html?t=1.2" frame-mid.png
    
  3. 各静止画を詳しく確認し、忠実度をチェックします。ストローク途中の t では、手のペン先が描画部分の先端にぴったり重なっていること(先行も遅れもなく、浮いていないこと)、パスが先端までだけ描画されていること、?t=<dur> ではすべてのストロークが隙間なく、描き過ぎも破線の残りもなく完了していることを確認します。アーティファクトも確認します。ストローク間に手が何もないボード上を滑る、ペン先の位置がずれて線から浮く、ストロークの順序が逆になる、輪郭より先に塗りが表示される、描画がクリップされる/キャンバス外にはみ出す、フォントの読み込み前に FOUC が発生する、といった問題です。
  4. 調整を繰り返します。NIB、ストローク順、表示時間を変更し、静止した各フレームが手描きの途中に見えるまでスクリーンショットを撮り直します。

完了前に確認すること:

  1. ブラウザーで単独起動できること。コンソールエラーや読み込めない CDN/アセットがないこと。
  2. マスタータイムラインが 1 つであること。?t=N で正しく静止し、strokeDashoffset と手の位置が同じ進行値を共有していること。
  3. 開始/ストローク途中/終了時点のスクリーンショットを撮影し、常に手のペン先が描画の先端に重なっていること、終了時にストロークがきれいに完了していること、描画順が正しいこと。
  4. ストローク間に手が上がり、何もないボード上を滑らないこと。
  5. prefers-reduced-motion の場合、描画アニメーションを再生せずに完成したボード(すべてのストロークを描画し、手を非表示にした状態)を表示すること。

ナレーション付きでフレーム単位の決定性を保った書き出しを行う場合は、同じ描画アニメーションを Remotion のコンポジションに移植し(strokeDashoffset と手の変形を useCurrentFrame()/fps から制御)、MP4/GIF にレンダリングします。レンダリング静止画 → エンコードのループについては explainer-video スキルを参照してください。

参考ファイル

  • references/draw-on-recipes.md — 実行可能な描画アニメーションの仕組み:getTotalLength/getPointAtLength による手のパス追従、GSAP の DrawSVGPlugin と MotionPathPlugin の各種パターン、複数ストロークのずらし、グリフ単位の手書き、輪郭の後の塗り、消去/ワイプと逆向き描画のトランジション、Remotion へのフレーム駆動移植。
  • references/whiteboard-pipeline.md — アートを描画可能な単一ストロークの SVG に変換する方法、描画順の作成、手書き/ストロークフォント、マーカーを持つ手のアセットとペン先の位置合わせ、ナレーションに合わせた描画時間の調整、ストーリーボードからボードを作成する手順。

コメント