雑記帳

整理しない情報集

更新情報

キャプチャAPIでスクリーンショットを撮ってみる

公開日:

カテゴリ: JavaScript

JavaScriptのAPIには画面共有向けの画面キャプチャAPIがあるため、そのAPIを使用してブラウザ画面のスクリーンショットを撮影してみます。

画面キャプチャAPI

JavaScriptの画面キャプチャAPIは主に3種類あります。いずれもPCのみ対応しており、モバイル系のブラウザでは2025年12月現在一切対応していません。

Screen Capture API

画面キャプチャAPIの中で、ベースとなるAPIです。画面キャプチャAPIは3種類あると書きましたが、実質的にあとの2種類はこのAPIに追加処理を組み合わせて実行するAPIです。

画面キャプチャ開始時にキャプチャ対象(ウィンドウ・タブ・画面のいずれか)をユーザが指定して、許可すると指定した画面のキャプチャができるようになります。キャプチャ内容はストリーム(MediaStream)として使用できます。

Region Capture API

Screen Capture APIで取得した画面キャプチャから、指定したDOMの表示領域を切り抜いたものをキャプチャできます。あくまで切り抜いているだけであるため、他のDOMが重なった場合は重なったDOMも一緒にキャプチャされます。

Element Capture API

Screen Capture APIで取得した画面キャプチャから、指定したDOMツリーのみをキャプチャできます。APIの使い方はRegion Capture APIとほぼ同じです。

サンプル

このサンプルでは、このページのメインカラム(右のサイドバーを含まない)部分のうち、画面に表示されている部分をキャプチャし、png形式でダウンロードします。

画面キャプチャを撮ってみる

今回はRegion Capture APIを用いて画面キャプチャを行います。Element Capture APIを使用したいところですが、筆者の環境では動かないのでRegion Capture APIを使用します。Chrome 132から使えるようになっているらしいのですが。Chrome開発ブログのデモMDNのサンプルも動かないので、今回は一旦置いておきます。

Region Capture APIの準備

まずはキャプチャする領域を指定するCropTargetオブジェクトを作成します。Screen Capture APIのみを使用する場合はこの手順は必要ありません。Element Capture APIを使用する場合はCropTargetの代わりにRestrictionTargetを使用します(引数の指定内容はCropTargetと同じ)。

const cropTarget = await CropTarget.fromElement(targetElem);

Screen Capture APIの実行

次にScreen Capture APIを実行します。APIの使い方は案外シンプルで、画面キャプチャAPInavigator.mediaDevices.getDisplayMedia()を実行するだけです。キャプチャ対象はユーザが決めるため、JavaScript側ではあまり設定できることは多くありません。

今回は現在開いているタブのキャプチャを取得したいのでpreferCurrentTabtrueに設定しています。このプロパティがtrueに設定されている場合は、ユーザはキャプチャ対象を選択する必要が無く、許可するか拒否するかのみを選択します。

const source = await navigator.mediaDevices.getDisplayMedia({
	video: {
		width: targetElem.offsetWidth,
		height: targetElem.offsetHeight
	},
	preferCurrentTab: true
});

ユーザが画面キャプチャを許可するとMediaStreamが返ってきて、画面キャプチャが開始されます。<video>要素のsrcObjectプロパティに代入すれば、そのまま動画として再生できます。

Region Capture APIの実行

今回はキャプチャ領域をDOMの位置で同じサイズに切り抜くため、MediaStream(ビデオソース)に対して先程作成したCropTargetを適用します。切り抜きが不要な(Screen Capture APIのみを用いる)場合はこの手順は不要です。Element Capture APIを使用する場合はcropTo()ではなくrestrictTo()RestrictionTargetのオブジェクト渡して実行します。

const [track] = source.getVideoTracks();
await track.cropTo(cropTarget);

キャプチャの取得

MediaStreamからキャプチャを行うにはMediaStream Image Capture APIを使用します。引数に映像トラックを渡します。

const imageCapture = new ImageCapture(track);

takePhoto()を実行することでBlobが、grabFrame()を実行することでImageBitmapが取得できます。takePhoto()を使用すると直接Blobが取得できるのでこちらを使用しようとしたのですが、なぜかエラーが出るのでgrabFrame()経由でBlobを取得します(実験中の機能ですし)。

const frame = await imageCapture.grabFrame();
const canvas = document.createElement("canvas");
canvas.width = frame.width;
canvas.height = frame.height;
canvas.getContext("2d").drawImage(frame, 0, 0);
const blob = await new Promise((resolve) => canvas.toBlob(resolve));

後は取得したBlobを煮るなり焼くなり。

後始末

MediaStream内の全トラックを停止すれば画面キャプチャAPIは終了します。今回のコードではトラックは映像トラック1つしか無いはずですが、念のため全トラックを取得して停止させています。

for (const track of source.getTracks()) {
	track.stop();
}

メモ

本項の内容は画面キャプチャを撮る用途に限定しません。

ある瞬間のキャプチャを撮る

Screen Capture APIは実行ごとに毎回許可が必要であるため、ある瞬間の画面キャプチャを取得することには向きません。一応あらかじめScreen Capture APIを実行した状態にしておけば、キャプチャしたいタイミングで画像データを取得することで実現できなくはなさそうです。ただし、この場合も画像データ取得APIは非同期であるため、厳密なフレームの取得は難しそうです。

また、Chromeではキャプチャ中はキャプチャの対象範囲に薄い枠が表示されます。これを常時表示させた状態にしておくのはUX的に少々憚られます。そもそもキャプチャAPI自体、少なくないリソースを消費するため、(今回のケースでは動画エンコードが走らないとはいえ)使用していない間も実行状態にしておくのは好ましくなさそうです。

幅・高さの指定

今回navigator.mediaDevices.getDisplayMedia()実行時の引数に幅・高さを指定しています。この値は省略可能ですが、省略した場合はブラウザ側が自動で拡大縮小を行うことがあるようです。

ブラウザからの共有停止

ブラウザから共有停止(ブラウザによっては共有中にブラウザ独自の停止ボタンが表示される)が行われた場合、MediaStreaminactiveイベントが発火されるようです。<video>要素に出力している場合、<video>要素ではsuspendイベントが発火されるようです。

今回のような一瞬しか使わない場合は使う機会がありませんが、Screen Capture APIで動画としてキャプチャする場合は考慮が必須となりそうです。

その他

記事内のWeb Featuresのステータスは、以下のスクリプトを用いて表示しています。

カテゴリ: JavaScript