html-demo について
React と DOM/CSS でアニメーション付き HTML ウィンドウを作るスキルで、`scripts/step-window.js` のバニラ版もあります。動画ではなく操作可能なウェブページ上のデモを作るチーム向けで、時刻からフレームを決めるため、ステップ移動やスクラブ、撮影、動きを抑えた終了状態に対応します。
主な機能
- 01時刻から決まるフレーム
- 02ステップ移動とスクラブ
- 03固定ステージと比例スケーリング
- 04カーソルとモーション用ヘルパー
- 05動きを抑えた終了フレーム
活用シーン
- 01ウェブページでアプリの操作を実演
- 02指定ステップで一貫した画像を撮影
- 03スクラブ可能なインタラクティブデモの表示
- 04動きを抑える設定向けの静的終了画面
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mengto/ skills --skill html-demoインストール後、Claude Code で/html-demoを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamldemo/index.htmlPROMPT.mdreferences/brief.mdcontract.mdfilm.mdscripts/example-steps.jsxpreview-plugin.mjsshoot.mjsstep-window.cssstep-window.jsStepWindow.jsx詳細
HTML デモ
React · DOM/CSS(バニラ版は scripts/step-window.js にあります)。動画、canvas、WebGL、画面録画は使用しません。
録画のように見えますが、これは HTML です。各フレームは計算によって生成されるため、どのサイズやピクセル密度でも鮮明さを保ち、画面上の文字は実際のテキストです。また、同じコードからループ再生、ステップへのジャンプ、スクラブ、スクリーンショット用の一時停止、モーション低減時の静止表示ができます。
使い分け。 実際のブラウザーを録画した MP4 が必要な場合は、browser-video-recording を使ってください。ページを長さの決まった映像にする場合は、website-to-motion-design を使ってください。アプリが動作する様子を表示するウェブページ上のライブウィンドウを成果物として作る場合は、このスキルを使ってください。
由来。 fil.design/download から抽出(2026年10月)。ここでは、Fil for Mac のヒーローウィンドウで、4 つのワークフローを最初から最後まで表示します。8 つのチャプターウィンドウでは、それぞれ 1 つの機能を段階的に表示します。URL のインポート → デザイン → エージェント、プロンプト → インペイント → アウトペイント → レイヤー → 角度、録画 → モーションデザイン → 編集 → トランスクリプト、サイト、ブラウザーです。リード 1 名がエンジンとヒーローを構築し、ヘルパーエージェント 1 つが共有ブリーフをもとに各チャプターウィンドウを構築しました。
仕組み
アプリのすべてのフレームは、1 つの数値だけから求められる純粋関数です。 ステップの View({ t }) は、ステップ内の経過秒数である t だけを使って、固定サイズ 1200 × 750 のデザイン領域にウィンドウ内のすべてを描画します。次に、1 つの CSS スケール(--k = width / 1200)で、その領域をウィンドウに合わせます。
この仕組みをなくすと、デモは動作しなくなります。状態を setTimeout の連鎖、アニメーションコールバック、エフェクトで管理すると、ステップにジャンプできません。非表示タブにした後はずれが生じ、スクリーンショットの内容は実行ごとに異なり、モーション低減時には途中の状態で止まります。ウィンドウ内をレスポンシブに設計した場合は、別の形で問題が起きます。幅が変わるたびにテキストが折り返され、ポインターはターゲットを外し、スマートフォンでは別のアプリのように表示されます。
import StepWindow, { Cursor, ease, typed, glide } from './scripts/StepWindow.jsx';
const ASK = 'The Ashen One, a knight in blackened armour resting on his sword';
function Prompt({ t }) { // t: 0 → dur, nothing else
const text = typed(ASK, t, 0.9, 2.6); // typed between 0.9 s and 2.6 s
const made = ease(t, 3.4, 5.2); // the picture draws in
const c = glide(t, [[0, 0, 900, 600], [0.2, 0.8, 520, 330], [2.8, 3.3, 760, 470]], [0.85, 3.35]);
return (<>
<App prompt={text} reveal={made} /> {/* the app's real markup at 1200 × 750 */}
<Cursor {...c} /> {/* last, over everything */}
</>);
}
export const STEPS = [
{ id: 'prompt', name: 'Prompt', dur: 6, View: Prompt },
// 3–5 steps, 4–8 s each
];
<StepWindow steps={STEPS} tabs={['The Ashen One']} label="Make a picture" />
scripts/StepWindow.jsx には、ウィンドウ、クロック(useStepClock)、スケーリング、ステップバー、Cursor、およびヘルパーの ease、typed、cue、held、glide が用意されています。完全な API は references/contract.md に、ヒーロー版(多数のフロー、カメラ、ビート)は references/film.md に記載されています。
ワークフロー
- 1つの題材を選び、すべての画面で使います。 製品、キャラクター、サイトなど、自分が所有するもっともらしい題材を1つ選びます(Fil は The Ashen One と Ashgrove を使いました)。すべての画面でそれを使ってください。クライアント、パートナー、実在の人物のキャンバスは決して表示しないでください。表示できない実名が並ぶ画面は使いものにならず、画面ごとに題材が違うと、寄せ集めに見えてしまいます。
- 描画を始める前に、実際の UI の参考資料を集めます。 スクリーンショット、ドキュメント内の録画(
ffmpeg -ss 2 -i in.mp4 -frames:v 1 out.pngでフレームを取り出します)、アプリのソースコードを使います。各ステップの参考資料をリストアップしてください。ラベル、メニュー項目、トースト、料金はすべて、記憶ではなくgrepでソースからそのままコピーします。アプリを知っている視聴者が、創作した言葉(「処理中…」など)を1つでも見つけると、ほかの内容も信じなくなります。 - 紙の上でステップを組み立てます。 1つの画面に3~5ステップを入れ、各ステップは4~8秒、全体で18~30秒にします。各ステップには、開始状態を確認する間(0.3~1秒)、操作、アプリの表示どおりに結果が現れる動き(処理中の状態から結果まで)、そして結果を見せたままにする時間(最低1秒)を設けます。ステップ名はバーに表示されるとおり、1~3語で付けます(「リンクを貼り付ける」「インペイント」など)。
- 各ステップを名前付きの時刻で記述し、
tから状態を導きます。TYPE_FROM = 1.9, SEND_AT = 4.3, DONE_AT = 6.9のような定数を使い、条件比較(t >= SEND_AT)、動きのある要素にはease(t, a, b)、タイピングにはtyped()、変更のリストにはcue()を使います。振り付けがスクリプトのように読み取れ、1か所の変更でタイミングを調整できるよう、時刻は冒頭に定数としてまとめます。 - その状態をもとに、1200 × 750 でアプリを描画します。 可能な限り、実際のアプリのマークアップと CSS を再利用します。そうでない場合は、スタイルシートからレール幅、トップバーの高さ、角丸、文字サイズをコピーします。すべてのクラスと
@keyframesに画面ごとのプレフィックス(ximp-、xpic-)を付け、ほかで使われていないことをgrepで確認します。 - 人らしいポインターの動きを付けます。 クリックの0.3~0.6秒前に到着し、0.15~0.2秒押し、ターゲット間を0.5~0.9秒かけて滑らかに移動します。操作の合間は邪魔にならない位置で止まり、視聴者に結果を見てほしいときはフェードアウトさせます。実際の要素(DOM から計測した
data-aim、またはスクリーンショットで確認した座標)を狙う必要があります。 - 撮影し、比較し、修正します。最低3ラウンド行います。 各ステップを通して0.5~1秒ごとにフレームを撮影し(
scripts/shoot.mjs)、参考資料と並べて比較します。「このアプリを使う人は、これが録画だと信じるだろうか?」と問いかけてください。仕上げ(動き)より先に、構造(レイアウト、サイズ、文言)を修正します。 - 端の状態も確認します。 モーションを減らす設定での最終フレーム、スマートフォンの幅(390 px と340 px)、非表示だったタブに戻ったときの状態、コンソールを確認します。
ルールと、それぞれが防ぐ失敗
tだけを基準に描画し、CSS トランジションは状態間を滑らかにつなぐ用途に限ります。 トランジションは直前のフレームに依存します。そのため、スクラブ時、停止時、撮影時に正確である必要があるもの(進行状況、入力済みテキスト、表示、カウンター、カメラのターゲット)はすべてtから取得します。ホバー、パネルのスライド、ポインターの移動にはトランジションを使ってかまいません。ただし、スクラブ後はトランジションが落ち着くまで、最大でそれぞれのトランジション時間分の遅れが生じます。ポインターも正確に合わせる必要がある場合はglide()を使います。- 固定ステージを単一のスケールで表示します。 1200 × 750(16:10、ウィンドウの表示領域の
aspect-ratio)を基準にデザインし、ResizeObserverからtransform: scale(var(--k))とtransform-origin: 0 0を使ってステージ全体を拡大縮小します。ウィンドウ内のレスポンシブレイアウトでポインターのターゲットが動くと、デバイスごとにアプリの見た目が変わってしまいます。 - 結果はアプリが実際に表示する方法で見せます。 処理中の状態(スピナー、「作成中…」、シマー、流れるスレッド)を見せてから、結果を表示し、最後に落ち着かせます。Fil の画像は上から描画します。暗くした自身の複製(
blur(10px) brightness(0.5) saturate(0.4)、1.08 倍に拡大)の上に、幅 2 px の暖色の走査線を重ねて、clip-path: inset(0 0 100% 0)からinset(0)へと表示します。その後、きらめきを加えながら、0.9 秒かけてbrightness(1.35) saturate(1.1)から落ち着かせます。結果がいきなり表示されるだけでは、スライドショーのように見えます。 - 実際の結果と実際の言葉を使います。 アプリで実際に生成され、ページの隣に保存された出力を表示します。代用品やダミーテキストは使いません。必要な結果がない場合は実際のアプリで作成し、費用がかかる場合は事前にオーナーに確認してください。作り物の結果は、まず最初に気づかれます。
- 時間を正直に圧縮します。 数分に及ぶ処理を数秒で再生しても、数値は正確に保ちます。Fil のヒーローでは、デモの 1 秒につき 18 秒を表示し(「5 つ作成、2:14」)、各ステップの完了に合わせてクレジット残高を減らします。
- 見えるときだけ再生します。 ウィンドウにはしきい値 0.3、大きなヒーローには 0.15 の
IntersectionObserverを使い、document.hiddenの間はティックをスキップし、dtを 0.25 秒に制限して、40〜50 ms ごとにティックします。フレームのシグネチャが変わったときだけ再描画します。ヒーローではJSON.stringify(frame)で比較します。内部の動画も表示状態に合わせて再生・一時停止します。これをしないと、ウィンドウが 8 つあるページでは画面外でも CPU を消費し、タブに戻ったときにステップ全体が早送りされてしまいます。 - 動きを抑える設定では、完成した終端フレームを表示します。
prefers-reduced-motion: reduceの場合、各ステップをt = durで表示し、ステージ上のすべてのアニメーションとトランジションを無効にし、ステップバーは引き続き機能させます(クリックすると、そのステップの終端を表示します)。そのため、それぞれの終端フレームは、それ単独で完成した見栄えのよい画像として作成してください。作成途中のフレームや空のウィンドウでは不十分です。 - クラスとキーフレームの両方で、ウィンドウごとに固有のプレフィックスを使います。 2 つのウィンドウでそれぞれ
.cardや@keyframes inを定義すると衝突し、後から読み込まれたスタイルシートがもう一方のウィンドウのスタイルも変更します。両方を読み込むページでのみ、この問題が表面化します。 - レイアウト効果の中で、DOM からターゲットを測定します。 値をハードコードしないでください。レイアウト後にポインターの狙い先(
[data-aim="headline"])とカメラのフレーム(フォーカス対象ノードのoffsetLeft/Top)を取得し、--kで割ります。座標をハードコードすると、フォントの読み込みやラベルの変更で最初からずれてしまいます。 - カメラを、測定に使ったシーンに紐づけます。 複数のキャンバスがある動画では、カメラの変換値をシーンのインデックスと一緒に保存し、そのシーンにのみ適用します。そうしないと、次のシーンが前のシーンの変換値を引き継ぎ、ステージ上を大きく移動してしまいます。
- 遅れて更新される表示を使ってシーンをクロスフェードします。 シーンが変わったら、古いシーンの最後のフレームを 300 ms のフェードアウトが終わるまで描画し、その後で新しいシーンを表示します。ステージのキーにはステップ ID を使い、各ステップを 0.45 秒かけてフェードインさせます。表示が遅れないと、古いシーンのフェード中に新しいシーンが一瞬表示されます。
- デモ内で実際のフォントを読み込みます。 ウィンドウ内に描画するページ(ランディングページやサイトなど)では、必要に応じて
<link id="…">を追加し、そのページ独自のウェブフォントを一度読み込みます。フォールバックフォントでは文字幅が変わるため、改行位置がずれてポインターの狙いが外れます。 - 再生中に次のシーンの画像を先読みします(
new Image().src = …)。そうしないと、キャッシュが空の状態で次のシーンが始まり、空のボックスが表示されます。 - 支援技術に画像として認識させます。 ステージに
role="img"と、ステップを列挙するaria-labelを設定し、ステージにはpointer-events: noneとuser-select: noneを指定します。ステップバーにはaria-current="step"と、見えるフォーカスリングを備えた実際のボタンを使います。スクリーンリーダーには、400 個の偽 UI ノードではなく、「ページをインポート:リンクを貼り付け、デザインで開く、エージェントで編集」と読み上げられるべきです。 <figure>にはmargin: 0が必要です。 ブラウザーのデフォルトの左右 40 px のマージンにより、幅 390 px のスマートフォンでは 80 px が失われます。
公開済みの数値
| 項目 | 値 |
|---|---|
| ステージ | 1200 × 750、--k = clientWidth / 1200 |
| ステップ | ウィンドウごとに3~5個、各4~8秒、ウィンドウごとに18~30秒 |
| 結果の表示時間 | ≥ 1秒 |
| 入力速度 | 約50文字/秒(1.8秒で90文字)。人が読む時間は確保し、入力完了を待たせません |
| ポインターの移動 | 0.5~0.9秒、cubic-bezier(0.55, 0, 0.25, 1)。到着は0.3~0.6秒早めます |
| クリック | 押下時間0.15~0.2秒。矢印のスケールは1.15 → 0.98 |
| ステップのフェードイン | 0.45秒、cubic-bezier(0.2, 0.7, 0.1, 1) |
| シーンのクロスフェード | 次のシーンが描画されるまで300 msの遅延 |
| クロック | ティック間隔は40 ms(ウィンドウ)または50 ms(フィルム)。dt ≤ 0.25 s。画面上の表示しきい値は0.3(ヒーローは0.15) |
| スレッド | ソースノードの色で、1.6 px、透明度0.8。動作中:破線 6 7 が0.7秒で流れます。停止中(短時間、スキル):0.14 |
| カメラ | 実行中のステップと、その入力元まで最大1.2×。キャンバス全体は0.86~1.08×。移動時間は1.3秒、cubic-bezier(0.45, 0, 0.2, 1) |
| 表示時間 | デモの1秒につき、アプリの時間を18秒表示 |
複数のウィンドウを同時に作る
まずエンジンとウィンドウを1つ、自分で作ります。このウィンドウをスタイルガイドにします。その後、各ヘルパーにウィンドウを1つずつ割り当て、共通ブリーフ(references/brief.md)を共有します。各ヘルパーが作成するのは、自分のプレフィックスを使った2つのファイルだけです。共有の開発サーバー(scripts/preview-plugin.mjs、/preview?entry=)でプレビューし、それぞれ専用のスクラッチフォルダーで作業して、gitは実行しません。各ヘルパーは、撮影、批評、修正を少なくとも3ラウンド行い、その後、手順、スクリーンショットのパス、「まだ足りない点」を報告します。
報告を読みます。そこに挙げられた不足点が、次のラウンドの課題です。ヘルパーは使用制限に達したら停止します。これまでの作業は保持されているため、新しいヘルパーを起動するのではなく、メッセージを送って再開します。
検証
- 同じ
tでフリーズしたフレーム(useStepClock(steps, { start: { i, t } }))が、毎回同じように表示されます。 - すべてのステップを0.5~1秒ごとに撮影し、実際のアプリの参照画像と並べて比較しました。
- UI内のすべてのラベル、メニュー項目、数字が、アプリのソースまたは参照画像内にあることを確認しました。
- 視差効果を減らす設定では終了時のフレームが表示され、ステップバーも引き続き動作します。
- 画面外またはタブが非表示のときは一時停止し、戻ってきたときに早送りされません。
- 390 pxおよび340 pxで横スクロールが発生せず、ウィンドウの縦横比が16:10に保たれます。
- クラスとキーフレームにはウィンドウのプレフィックスのみが使われています(grepで確認済み)。
- コンソールに問題がありません。
ファイル
scripts/StepWindow.jsx、scripts/step-window.css:エンジン(React)。ウィンドウ、クロック、スケール、ステップバー、ポインター、ヘルパーが含まれます。scripts/step-window.js:Reactを使わない同じエンジン(HTMLDemo.mount)。同じCSSを使用します。scripts/example-steps.jsx:コントラクトに沿って記述した3つのステップ(プロンプト → 生成 → 全体表示)。scripts/preview-plugin.mjs:各ウィンドウを単独でプレビューできるよう、/preview?entry=<file>を提供するViteプラグイン。scripts/shoot.mjs:指定した秒数またはフリーズしたフレームを、2×で撮影するPlaywrightスクリーンショット。references/contract.md:ステップのコントラクト、クロックAPI、ヘルパー。references/film.md:ヒーロー向けのバリエーション(グラフからスケジュールされたフロー、キュー付きのビート、計測されたカメラ)。references/brief.md:ヘルパーエージェント向けの共通ブリーフのテンプレート。demo/index.html:Fil for Macで画像を作成する、スクラバー付きの自己完結型デモ(バニラ)。