ブラウザ操作を自動キャプチャして手順書を作るChrome拡張の作り方:Procshot開発記録
「この操作、どうやるんでしたっけ?」
社内システムの使い方を聞かれるたびに、スクショを撮って、赤枠つけて、「ここをクリックします」と書いて...。気づけば30分経っている。
この無駄な時間をなくしたくて、ブラウザ操作を自動でキャプチャして手順書を作るChrome拡張機能を作りました。
https://chromewebstore.google.com/detail/手順キャプチャ-手順書・操作マニュアル自動作成/ieblehdloggcpmkncplccjofeoakhkll
手順キャプチャ(英語名: Procshot)
「撮影開始」ボタンを押して普通にブラウザを操作するだけで、スクショ付きの手順書が自動で完成します。
-
📸 クリック・入力・ページ遷移を自動検出してスクショ撮影
-
✏️ 「検索ボタンをクリックします」など説明文を自動生成
-
🎯 クリック位置を枠や矢印で強調
-
📤 HTML / Markdown / PDF でエクスポート
完全ローカル動作: スクショや操作データは外部に送信されません
日本語ネイティブ対応: 説明文が自然な日本語で生成されます
$9/月: 競合(Scribe $23、Tango $20)より安い
Chrome Extension (Manifest V3)
├── React 18
├── TypeScript
├── Vite + CRXJS
├── Tailwind CSS
└── IndexedDB (idb-keyval)
サーバーレスで、すべての処理がブラウザ内で完結します。
最初は「クリック後にスクショを撮る」実装にしていましたが、これだと問題が発生しました。
ユーザーがボタンをクリック
↓
ページが遷移
↓
スクショ撮影 ← この時点でクリックしたボタンはもう画面にない!
解決策として、クリック前にスクショを撮るように変更しました。
document.addEventListener('click', async (event) => {
// 1. クリック前に即座にスクショ
const screenshot = await captureVisibleTab();
// 2. 要素情報を取得(まだDOMにある状態で)
const element = event.target;
const rect = element.getBoundingClientRect();
// 3. ステップとして記録
saveStep({ screenshot, rect, ... });
// 4. クリックは通常通り実行される
}, true);
ユーザーが の中の の中の `` をクリックした時、イベントターゲットはどれになるか?
答えは「場合による」です。イベントバブリングにより、実際にクリックした要素より親要素がターゲットになることがあります。
そこで、意味のある要素を探索するロジックを追加しました。
function findMeaningfulElement(target: Element): Element {
const genericTags = ['DIV', 'SPAN', 'BODY', 'SECTION'];
// ターゲットが汎用要素なら、子要素から意味のある要素を探す
if (genericTags.includes(target.tagName)) {
const button = target.querySelector('button, a, input, [role="button"]');
if (button) return button;
}
return target;
}
クリックした要素から「何をクリックしたか」を説明するテキストを生成します。
優先順位:
aria-label 属性
placeholder 属性
innerText(アイコン要素を除外)
- タグ名からの推測(「ボタンをクリックします」)
function getElementText(element: Element): string {
// aria-labelを優先
const ariaLabel = element.getAttribute('aria-label');
if (ariaLabel) return ariaLabel;
// placeholderをチェック
if (element instanceof HTMLInputElement && element.placeholder) {
return element.placeholder;
}
// innerTextからアイコンを除外して取得
const clone = element.cloneNode(true) as Element;
clone.querySelectorAll('svg, [class*="icon"]').forEach(el => el.remove());
return clone.textContent?.trim() || '';
}
ただし、これだと「open_in_new」(Material Iconのaria-label)などが混入することがあったので、除外パターンも追加しています。
ReactやVueなどのSPAでは、従来の beforeunload イベントが発火しません。代わりに History API を監視します。
// pushState/replaceState をラップして検出
const originalPushState = history.pushState;
history.pushState = function(...args) {
originalPushState.apply(this, args);
handleNavigation();
};
// popstate(ブラウザの戻る/進む)も検出
window.addEventListener('popstate', handleNavigation);
撮影中であることをユーザーに伝えつつ、オーバーレイ自体がスクショに映り込まないようにする必要がありました。
const overlay = document.createElement('div');
overlay.setAttribute('data-procshot-ignore', 'true');
document.body.appendChild(overlay);
// スクショ撮影時にオーバーレイを一時的に非表示
async function captureScreenshot() {
overlay.style.display = 'none';
const screenshot = await chrome.tabs.captureVisibleTab();
overlay.style.display = 'block';
return screenshot;
}
Chrome拡張のManifest V3では、Service Workerが非アクティブ時に停止します。これにより、録画状態の管理が複雑になりました。
// Service Workerが再起動するたびに状態を復元
chrome.storage.local.get('recordingState', (result) => {
if (result.recordingState?.isRecording) {
// 録画を再開
resumeRecording(result.recordingState);
}
});
Chrome Web Storeの審査では、権限の正当性が厳しくチェックされます。`` 権限を使う理由を明確に説明する必要がありました。
この拡張機能は、ユーザーが閲覧しているWebページでスクリーンショットを撮影し、クリック位置を検出するために、すべてのURLへのアクセス権限が必要です。
AI による説明文の改善(Gemini API 連携)
インタラクティブ再生(実際のページ上でガイド表示)
チーム共有機能
個人開発で14個目のChrome拡張機能になりました。
「こういう機能がほしい」「ここがおかしい」などフィードバックいただけると嬉しいです!
https://chromewebstore.google.com/detail/手順キャプチャ-手順書・操作マニュアル自動作成/ieblehdloggcpmkncplccjofeoakhkll
S-Hub では、生産性向上のためのツールを開発しています。
他の拡張機能もぜひチェックしてみてください。
PromptStash - AIプロンプトを保存・管理(19サービス対応)
DataPick - Webページからデータを抽出
DataBridge - ページ間データ転送
SnippetVault - コードスニペット管理
ReadMark - 読書位置を記憶
YouTube Shorts Killer - YouTubeショートを非表示
X Detox - X(Twitter)のノイズを消す
ZenRead - 集中読書モード
楽天セラーズ・アナリティクス - 楽天市場の出品者分析
Arbitra - 価格比較ツール
Japanese Font Finder - Webサイトの日本語フォントを特定
TVerPlus - TVerの視聴体験を改善
Yahoo快適 - Yahoo!の閲覧体験改善
物件賃貸分析 - 賃貸物件の分析
物件購入分析 - 購入物件の分析
すべての拡張機能の詳細 → S-Hub
Discussion