asopi tech
asopi techIndie Developer
AstroのMarkdownにアニメーション図を埋め込む:Starch DSLと共通コンポーネント

【2026年8月版】

AstroのMarkdownにアニメーション図を埋め込む:Starch DSLと共通コンポーネント

公開日: 2026/08/19
読了時間: 約 11分

ブログ記事で抽象的な概念や複雑な処理フローを分かりやすく説明するため、アニメーション表現を取り入れました。

本サイトでは、StarchのDSLで定義した図をMarkdownから表示する<animated-diagram>コンポーネントを追加しました。この記事では、実際の表示例と、Starchをどうラップして記事へ組み込んだかを紹介します。

Starchとは

Starchは、図形・経路・時間変化をテキストで定義し、その定義からブラウザー上にアニメーション図を描画するDSLです。

図形と経路をobjectsに、時間による変化をanimateに記述します。次の例では、線の上に置いたpacketを2秒かけて始点から終点へ移動させます。

DSL

name "Starch basics"
background #071015
viewport 480x260

style routeStyle
  stroke #557c50 width=3

objects
  route: path (60,120) (420,120) @routeStyle
  packet: ellipse 24x24 fill #a6d65f pathFollow=route pathProgress=0
  start: text "start" size=14 fill #eaf4f6 at 60,165
  end: text "end" size=14 fill #eaf4f6 at 420,165

animate 2 loop easing=linear autoKey=false
  0 packet.transform.pathProgress: 0
  2 packet.transform.pathProgress: 1

Starchのレンダリング結果

objectsで経路とパケットを定義し、animatepathProgress0から1へ変化させています。Starchはこの定義を読み込み、現在の時刻に対応する図をSVGとして描画します。

AIに実装させる難しさではなく、何を指示し続けるか

再生ボタンやシークバーの実装自体は、この導入の中心ではありません。用途が決まったUIなら、コーディングエージェントでも実装できます。問題だったのは、図を修正するたびに、表現したい意味をCanvasとGSAPの細かな変更へ落とし直すことでした。

Starchへ移す前のMCPアニメーションは、専用コンポーネント2本、共通再生処理、動画出力処理の4ファイルで合計641行ありました。そこには次の指定が実装として埋め込まれていました。

  • 1072×720の構図と、Agent、Router、Instance、DB、ラベルの座標
  • Stateful側とStateless側で異なる折れ線経路
  • 経路上を移動するpacketの補間と、複合packetを水平に保つ描画
  • 0.8秒ごとのA→B→C配送、2.4秒の移動、0.12秒の保持
  • 到着時に切り替わるactive枠、色、発光、進捗表示
  • 認証失敗、token発行、instanceごとの認可、DB到達を分けた状態遷移
  • 任意時刻から同じframeを作る動画出力用のrenderAt(seconds)

たとえば1本目では、配送経路を座標配列で持ち、packetAt()で線分ごとの長さを計算してpacket位置へ補間し、GSAPのstate値が.72.83を越えた時点でInstanceのactive表示を切り替えていました。2本目ではpayloadSpeed = 160から認可経路の所要時間を計算し、拒否、認証、認可、DB到達を別々のtimelineとして組み立てていました。

この実装をAIに書かせることはできます。ただし、図の意図を変えるたびに「どの経路を通るか」「何秒でどの状態へ移るか」「その瞬間に何を強調するか」を、描画コードとtimelineの修正として指示し、結果を目視で確認する必要があります。

Starchで変えたのは、実装能力ではなく指示とレビューの単位です。図の構造と経路はobjects、時刻ごとの状態はanimateへ書き、同じ時刻から同じSVGを生成します。これにより、変更内容をCanvas APIの差分ではなく、図に置く対象、経路、状態、時刻として確認できます。

ただし、DSLへ移せば必ず短くなるとは確認できていません。1本目は専用コンポーネント319行に対して、書き直したvanilla Starch DSLも160行あります。2本目のDSLは377行です。図を見てから作ったShapeSetやhelperを別ファイルへ移して、短くなったことにはしません。今回確認できた価値は、図の意味と時間を宣言的なsourceへ分離し、同じ入力と時刻から同じ状態を再現できることです。

動画ではなくDSLを読み込む

記事へ埋め込んだ図です。再生位置のつまみを動かしてみてください。

読者のブラウザーがDSLを読み込み、リアルタイムにSVGへアニメーションを描画します。動画のように完成したファイルを再生しているのではありません。再生中は、Starchが各フレームの図をDSLの定義からその場で組み立て、SVGを書き換えています。

再生位置を移動したときも同じです。移動先の図をDSLから組み立て直して描き直すため、途中の状態をそのまま確認できます。動画のように、あらかじめ用意されたコマを送っているのではありません。

Markdownから表示する

今回の実装では、記事側にカスタム要素を書き、DSLの公開URLと図の説明を渡します。

<animated-diagram
  src="https://example.com/diagram.starch"
  aria-label="Stateful MCPとStateless MCPの比較アニメーション"
  autoplay
></animated-diagram>

srcでDSLの公開URLを指定し、aria-labelで図の内容を説明します。記事側には、図の定義と説明を渡す記述だけを置く形にしました。

Starchをどうラップしたか

Starchには図の描画を任せ、本サイトのコンポーネントで記事向けの表示と操作を加えました。カスタム要素がブラウザーへ接続されるとDSLを取得し、StarchDiagramに渡します。

実装の中心部分は次のコードです。

const source = this.getAttribute('src');
if (!source) {
  this.showError(`${this.copy.failed} src is required.`);
  return;
}

const response = await fetch(source, { signal: this.abortController.signal });
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);

const dsl = await response.text();
this.diagram = new StarchDiagram(stage, {
  autoplay: false,
  onEvent: () => this.syncControls(),
});

const result = this.diagram.setDSL(dsl);
if (!result.ok) throw new Error(result.error);

this.dataset.ready = 'true';
this.dataset.warningCount = String(result.warnings.length);
this.dataset.duration = String(this.diagram.duration);
this.range.max = String(this.diagram.duration);

fetchでDSLを取得し、StarchDiagramを描画先のステージへ接続して、setDSLで解析します。解析に失敗した場合はエラーを表示し、成功した場合は再生時間をシークバーへ渡します。StarchがSVGを描画し、コンポーネントが記事に必要な操作を受け持つ分担です。

コンポーネントとして追加した機能

このコンポーネントには、アニメーション図のアクセシビリティを高め、再生・停止・シークなどを操作できるようにする機能をまとめました。

  • 再生、一時停止、最初からの再生、シーク、章移動、拡大表示
  • role="img"aria-label、操作ボタンのラベル、再生状態の通知
  • prefers-reduced-motionが有効な環境での静止表示
  • 読み込みに失敗したときのエラー表示と、JavaScriptが無効なときの代替文
  • 画面幅に合わせた縮小表示と、ポインター操作で押しやすいボタン

図ごとに異なるのはStarchの定義と説明です。再生やアクセシビリティの処理は共通コンポーネントにまとめたため、記事ごとに同じ処理を実装せずに済みます。

VHSとStarchで埋め込み方を変えた理由

VHSでは、Astroのビルド時にVHSの台本からMP4・GIF・PNGを生成し、生成したファイルをHTMLとして記事に表示します。ブラウザーは生成済みのファイルを再生・表示します。

Starchでは、完成した動画ではなくDSLを読者のブラウザーで読み込みます。<animated-diagram>がDSLを取得し、StarchDiagramへ渡して、リアルタイムにSVGへアニメーションを描画します。再生位置の変更や動きを減らす設定など、表示中の状態もコンポーネントが管理します。

この違いに合わせて、VHSでは生成済みメディアを指定するMarkdownのvideoコードブロックを使い、Starchではブラウザーで動くカスタム要素を使いました。StarchのDSL例は読みやすさのためコードブロックで示し、実際のアニメーション表示には<animated-diagram>を使っています。

まとめ

本サイトでは、抽象的な概念や複雑な処理フローを記事で説明するため、Starchのアニメーション図をMarkdownへ埋め込むコンポーネントを追加しました。

  • Starchで図形、経路、時間変化を定義する
  • コンポーネントがDSLを読み込み、StarchでSVGを描画する
  • 共通コンポーネントで操作、アクセシビリティ、エラー表示を提供する
  • MCPの構成と状態変化をアニメーションで説明する

参考