ホワイトボードアニメーション は何をしますか?
VideoScribe / Doodly の見た目:マーカーハンドがボード上にイラストや手書き文字をストロークごとに描き、ナレーションが説明します。これを、描画するアニメーションSVGパスと、描画先端を追跡するマーカーハンドで構築し、ナレーション順に要素を配置してボードが埋まるようにします。
使用するタイミング
- 手でアートやテキストをホワイトボードに描く「スクライブ」/ doodle 解説。
- スケッチやロゴがライブで、線ごとに描かれるアニメーション。
- 音声に同期して、手書き文字が自身で書き込まれる。
- 静的なイラストを、ガイド付きのストロークごとのリビールに変換する。
コアメカニズム:SVGストローク描画
stroke-dashoffset をフル長さから0までアニメーションさせることで、パスが「自分自身を描画」します。ダッシュのギャップが縮まり、ストロークが始点から終点に向かって露出します。
const path = document.querySelector("#stroke");
const len = path.getTotalLength();
path.style.strokeDasharray = len; // パス全体と同じ長さの1つのダッシュ
path.style.strokeDashoffset = len; // 完全に視界外に押し出される = 非表示
path.getBoundingClientRect(); // トランジション前にリフローを強制
path.style.transition = "stroke-dashoffset 1.2s linear";
path.style.strokeDashoffset = "0"; // 始点→終点を描画
<path> に pathLength="1" を使用して正規化します:すると stroke-dasharray:1; stroke-dashoffset:1 で実際の長さに関係なく任意のパスを描画でき、単一のイージング値(0→1)が描画の進行度になります。linear(または非常に緩やかなイージング)は安定した手の動きとして読み取られます。長いストロークで ease-in-out は避けてください — マーカーが急に動くように見えます。
マーカーハンドが先端を追跡する
錯覚の成否は、ハンドが描画されている正確な点に配置されることに依存します。現在の描画進行度でパスをサンプリングし、毎フレームそこにハンド画像を配置します。ハンドアセットのペン先は、パスの点に合っている必要があります(画像の角ではなくペン先が点に来るようにオフセットします)。
const hand = document.querySelector("#hand"); // <img>/<g>、先端が左上の「ペン先」
const NIB = { x: 6, y: 4 }; // ハンドアート内のペン先オフセット
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 進行度
path.style.strokeDashoffset = len * (1 - k); // 始点→先端を露出
const p = path.getPointAtLength(len * k); // SVG座標での現在の先端
hand.setAttribute("transform", `translate(${p.x - NIB.x} ${p.y - NIB.y})`);
if (k < 1) requestAnimationFrame(frame);
})(t0);
}
getPointAtLength(len * k) が主力です:strokeDashoffset を駆動している同じ進行度で、パス上の座標を返すので、ペン先が成長するストロークに張り付いたままになります。別々のストロークの間ではハンドを持ち上げます(フェードアウトして、次の開始点にジャンプ)。そうしないと、ハンドが空白のボード上を滑って見えます。
表示順序 — ボードが埋まる
人がシーンを描くように、ナレーション順に要素を配置します:各イラストと各手書きブロックが描画され、ホールドし、次が始まります。1つの描画内でストロークをずらします。各要素が着地した後、ハンドを動かす前に一拍おきます。1つの描画 = VOの1ビートです。2つのものを同時に描かないでください — 目(そして単一のハンド)は1つの先端しか追跡できません。
| ステップ | メカニズム |
|---|---|
| ストロークを描画 | stroke-dashoffset len→0、getPointAtLength(len*k) にハンド |
| マルチストローク描画 | ストロークをずらす。ハンドは各新しいストロークの開始点にホップする |
| 手書き | 読み順に描画される文字/単語のストローク、ハンドが追従 |
| ホールド | 要素完了後、ハンドをボード外で一時停止(0.4~1.0秒) |
| 次の要素 | ハンドを次の開始点に移動/フェード。その描画を開始 |
| 消去 / クリア | 次のセクションのためにボードをクリアするためにワイプまたは逆描画 |
アートを描画可能なパスに変換する
描画には、描画順の単一ストロークのオープンパスが必要です — 塗りつぶされた複合シェイプではありません。
- 線画をオープンストロークとして作成またはトレースします(Illustrator/Inkscape:アウトライン、塗りつぶしではありません)。塗りつぶされたブロブにはアニメーションする「ストローク」がありません。塗りつぶしを中心線のストロークに変換するか、アウトラインパスを描画し、アウトライン完了後に塗りつぶします。
- 順序が重要です:
<path>要素は、人が描く順序でSVGに現れる必要があります — ドキュメント順でアニメーションさせます。 - 長く連続した線が最も自然に見えます。実際のペンが持ち上がる場所でのみ分割します。
- ベタ塗り領域の場合:最初にアウトラインパスを描画し、次にその背後で塗りつぶしをフェード/拡大します(
opacityまたはクリップリビール)。「後で色を塗った」ように見えます。 - 手書き:ストローク/手書きフォントを使用し、グリフをパスに変換し、各グリフのパスをストローク順に描画します。または、ごまかします:単語をマスクし、ハンドをマスクエッジに沿わせて左から右へリビールをスイープします。
マーカーハンドアセット
マーカーを持った手のPNG/SVG画像で、ペン先が既知の点にあるもの。ペン先オフセットを一度測定し(上の NIB)、再利用します。一貫性のために、作品全体で1つのハンドアセットを使用します。左手が必要な場合のみミラーリングします(scaleX(-1))。ハンドの下に薄い影を追加して立体感を出します。ハンドが「オフ」のホールド中は影を削除します。
消去 / ワイプ遷移
セクション間でボードをクリアして、ごちゃごちゃしないようにします:
- ワイプ:塗りつぶされたグループ全体に
clip-path(または白い長方形)をアニメーションさせて拭き取り、ハンドはオプションで「消去」。 - 逆描画:
strokeDashoffsetを 0→len で実行して描画を戻し、ハンドは後ろ向きに追従。 - フェード+再描画:古いグループをクロスフェードアウトし、次のグループの描画を開始。
一貫した文法を1つ保ちます(常に左から右にワイプする、など)。そうすることで遷移が意図的に感じられます。
ナレーションへのペーシング
描画時間は、固定タイマーではなく、話されているセリフに追従する必要があります。VOから約2.3語/秒で見積もります。各描画の合計描画時間を、それが示すセリフの長さにおおよそ設定し、さらに短いホールドを追加します。VOが名前を呼ぶラベルは、その単語が話されるちょうどその時に描画が完了するようにします。強調線ではハンドを遅くします。重要な接続ストロークは速くします。完成したボード上にハンドをアイドル状態にしたままナレーションが続かないようにします — ハンドを動かすか、次の要素を開始します。
出力チェックリスト
- すべてのストロークは単一のオープンパスで描画順になっています。何も完全な形でポップインしません。
- 各ストローク中、ハンドのペン先が描画先端に正確に位置しています(浮かず、遅れず)。
- 個別のストローク/要素間ではハンドはきれいに持ち上がります。空白のボード上を滑りません。
- 要素はナレーション順に、一度に1つずつ描画されます。それぞれ次の前にホールドがあります。
- 手書きは読み順で描画されます。塗りつぶしはアウトラインの後に表示されます。
- 消去/ワイプ文法は一貫しています。ボードが過度にごちゃごちゃすることはありません。
- 描画タイミングは、それが示すVOのセリフに追従します。
prefers-reduced-motion:描画アニメーションなしで、完成したボード(すべてのストロークがオフセット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を参照。
自己完結型のホワイトボードシーン(描画イラスト、手書き、短いスクライブビート)の場合、成果物はブラウザで直接開く1つのHTMLファイルです — ビルドステップやレンダリングパイプラインは不要です。Webモーションには単一ファイルが適切なレベルです。1つのファイルで済むのにバンドラーに手を出さないでください。(焼き込みVO付きのフルナレーションスクライブビデオの場合は、Remotionコンポジションとして構築し、remotion still で検証してください — explainer-video を参照。)
出力契約:
- 1つの
.htmlファイル:インラインSVG(描画順のパス、pathLength="1"または実測長)、インラインまたはデータURIのハンドアセット、1つのインライン<script>内の描画ドライバー。 - すべての描画は1つのマスタータイムライン上(GSAP
tl、または1つのrAFクロック)。これにより、シーケンス全体が単一の再生ヘッドを持ち、シーク可能になります —strokeDashoffsetとハンド位置の両方が同じ進行値で駆動されます。 - 以下のシークハーネスを含め、任意の瞬間をスクリーンショット用にフリーズできるようにします。
tでフリーズすると、パスのどの程度が描画されたかが正確にわかります — 検証はこれに依存します。
シークハーネス — スクリーンショット用に正確な瞬間をフリーズ。 ?t=N はマスタータイムラインを N 秒にシークして一時停止するため、スクリーンショットは描画途中の静止した決定論的なフレームをキャプチャします。
<script>
// ... マスタータイムラインを `tl` として構築(drawOn呼び出しを配置) ...
const t = new URLSearchParams(location.search).get("t");
if (t !== null) { tl.pause(); tl.seek(parseFloat(t)); } // t秒でフリーズ
// ?t なし → 通常再生
window.__ready = true; // ヘッドレス待機用の準備信号
console.log("duration", tl.duration());
</script>
検証ループ — レンダリング → フリーズ → スクリーンショット → チェック:
- タイムライン上の瞬間(開始、描画途中のフレーム、終了)でファイルを開く:
…/scribe.html?t=0、?t=<dur/2>、?t=<dur>。終了時間はコンソールのtl.duration()から読み取ります。少なくとも1つのtをストロークの途中で選びます — ここでハンドオンチップの錯覚が証明または破綻します。 - 各フリーズフレームをヘッドレスでスクリーンショット:
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/scribe.html?t=1.2" frame-mid.png - 各静止画を検査 — 忠実度を確認:ストローク途中の
tで、ハンドのペン先が描画部分の最先端に正確に位置している(先行していない、遅れていない、浮いていない)。パスは先端までのみ描画されている。?t=<dur>ですべてのストロークがきれいに完了し、ギャップ、オーバーシュート、残ったダッシュがない。アーティファクトを確認:ストローク間でハンドが空白のボード上を滑っている、ペン先オフセットが間違っている(先端が線から浮いている)、ストロークが順序通りに描画されていない、アウトラインの前に塗りつぶしが表示されている、クリップ/キャンバス外の描画、フォント読み込み前のFOUC。 - 繰り返す:
NIB、ストローク順、時間を調整し、各フリーズフレームが手描き途中のように見えるまで再スクリーンショット。
終了前に:
- ブラウザでスタンドアロンで開く — コンソールエラー、CDN/アセットの欠落なし。
- 1つのマスタータイムライン。
?t=Nで正しくフリーズし、strokeDashoffsetとハンド位置が1つの進行値を共有している。 - 開始/ストローク途中/終了でスクリーンショット — ハンドのペン先が全体を通して先端にあり、ストロークは終了できれいに完了、描画順が正しい。
- ストローク間でハンドが持ち上がる。空白のボード上を滑らない。
prefers-reduced-motionは、描画アニメーションなしで完成したボード(すべてのストローク描画済み、ハンド非表示)を表示する。
ナレーション付きのフレーム確定エクスポートについては、同じ描画ロジック(useCurrentFrame()/fps から strokeDashoffset とハンド変換を駆動)をRemotionコンポジションに移植し、MP4/GIFにレンダリングします — レンダリングスチル→エンコードループについては explainer-video スキルを参照。
リファレンスファイル
references/draw-on-recipes.md— 実行可能な描画メカニズム:getTotalLength/getPointAtLengthハンド追跡パス、GSAPDrawSVGPluginおよびMotionPathPluginのバリアント、マルチストロークのずらし、グリフ単位の手書き、塗りつぶし後のアウトライン、消去/ワイプと逆描画トランジション、およびRemotionフレーム駆動ポート。references/whiteboard-pipeline.md— アートを描画可能な単一ストロークSVGに変換する、描画順の作成、手書き/ストロークフォント、マーカーハンドアセットとペン先キャリブレーション、描画時間をVOのセリフに合わせる、ストーリーボードからボードへの構築シーケンス。