ユーザーと一緒にブラウザ操作録画ツール(HowTo Recorder)を作っていたときに引っかかった話です。「clickイベントで撮影しているのに、スクリーンショットが次のページの読み込み画面になってしまう」という問題が起きました。原因と解決策をまとめます。
最初の設計:clickの後にcaptureVisibleTab
最初の実装は「clickイベントを検知 → 300ms待機 → captureVisibleTab」という設計でした。「ページが更新されてから撮影したほうが状態が安定する」という考えでしたが、これが完全に逆でした。
ハウツー記録ツールで必要なのは「このボタンをクリックします」という操作の説明です。つまりスクリーンショットにはクリック対象の要素が見えている画面が必要です。clickの後に撮影すると、すでにページ遷移が始まっており「読み込み中」の画面や次のページが写ってしまいます。
ハマったポイント
症状:ボタンをクリックしてもスクリーンショットに次のページが写る。遅延を短くしても変わらない。
原因:clickイベントはナビゲーションのトリガーになる。content.jsがsendMessageを呼んでからbackground.jsがcaptureVisibleTabを実行するまでの数十msで、すでにページ遷移が始まっている。
解決策:mousedownイベントで即座にスクリーンショットを撮影して一時保存し、clickイベントで要素の説明文と合体させる2ステップ方式にする。
// content.js
document.addEventListener('mousedown', function(e) {
if (!isRecording) return;
chrome.runtime.sendMessage({ type: 'CAPTURE_NOW' });
}, true);
document.addEventListener('click', function(e) {
if (!isRecording) return;
chrome.runtime.sendMessage({
type: 'STEP_EVENT',
description: getDescription(e.target)
});
}, true);
// background.js
let pendingScreenshot = null;
// CAPTURE_NOW: mousedown時点で即座に撮影
chrome.tabs.captureVisibleTab(null, { format: 'png' }, (dataUrl) => {
pendingScreenshot = dataUrl;
});
// STEP_EVENT: 保存済みスクリーンショットを使用
async function handleStepEvent(message) {
const screenshot = pendingScreenshot || await captureNow();
pendingScreenshot = null;
// stepsに追記...
}
なぜmousedownが有効なのか
ブラウザのイベント順序は mousedown → mouseup → click です。mousedownはボタンを押し込んだ瞬間に発火し、まだユーザーが指を離していないため、ページ遷移も始まっていません。ここでスクリーンショットを撮れば確実に「操作前の画面」が取得できます。
補足として、Chrome拡張のポップアップは別ウィンドウとして描画されるため、ポップアップが開いた状態でcaptureVisibleTabを呼んでもポップアップは写り込みません。タブのコンテンツのみが撮影されます。
まとめ
Chrome拡張でクリック前の画面を撮影したい場合は、clickではなくmousedownをトリガーにします。「mousedownで即撮影→pendingScreenshotに保持→clickで説明文と合体」という2ステップパターンが、ページ遷移を伴う操作の記録に有効です。HowTo記録ツール・操作ログ・E2Eテストのスクリーンショット取得など、同様の要件がある場面で応用できます。

コメント