JavaScript で paste イベントを活用して画像、ファイル、HTML をブラウザに貼り付ける
paste イベントを活用して画像やファイル、HTML 等さまざまな貼り付けられた情報を簡単に JavaScript で利用する方法を紹介します。
やりたいこと
Web ページを HTML として <input type="text"> や <textarea> に貼り付け、スクレイピングのように構造を解析して必要なデータだけ取り出すといったことがしたかったのですが、<input type="text"> や <textarea> は Web ページをコピーして貼り付けるとプレーンテキストとなり文書構造やスタイル情報は落とされてしまいます。コピー&ペーストで HTML の情報を落とさず取得する方法はないかと色々試した中で paste イベントの活用が最も使いやすいと思ったので紹介します。
他に HTML 要素の contenteditable 属性(contenteditable - HTML: ハイパーテキストマークアップ言語 | MDN)や クリップボード API(Clipboard - Web API | MDN)も考えたのですが、前者は今回の目的だけだと使えそうではあったのですが、ファイルが取り扱えなかったので見送り、後者は https 環境が求められるので見送りました。
環境
- ブラウザ
- Microsoft Edge 121.0.2277.112
paste イベント
pasteイベントは、ユーザーがブラウザーのユーザーインターフェイスで「貼り付け」操作を行ったときに発生します。
カーソルが編集可能なコンテキストにある場合(<textarea>やcontenteditable属性がtrueの要素など)、既定のアクションはクリップボードの内容を文書のカーソル位置に挿入します。
- Element: paste イベント - Web API | MDN
さっそくやってみましょう。
<textarea></textarea>
<script>
document.querySelector('textarea')
.addEventListener('paste', (e) => console.log(e));
</script>適当な画像ファイルをクリップボードにコピーした状態で、この HTML をブラウザで開いて <textarea> に貼り付けてみます。ブラウザ開発者ツールのコンソールを見てみると ClipBoardEvent(ClipboardEvent - Web API | MDN) が表示されています。
ClipboardEvent は他のイベントにはない clipboardData(ClipboardEvent: clipboardData プロパティ - Web API | MDN)というプロパティーを持っています。
clipboardData プロパティーは DataTransfer(DataTransfer - Web API | MDN)オブジェクトとなっていて、items というプロパティーに貼り付けされた項目が DataTransferItem(DataTransferItem - Web API | MDN)オブジェクトとして配列ライクに格納されています。
DataTransferItem は貼り付けられた内容の形式(通常は MIME タイプ)単位に作成されるので注意です。例えば Web ページをコピーして貼り付けた場合は、text/plain と text/html の 2 つの MIME タイプを有するため、clipboardData.items.length は 2 になります。また、画像も Word や PowerPoint から貼り付けると、image/svg+xml、 image/png のように 2 つの MIME タイプを持つため、同様に clipboardData.items.length は 2 になります。
貼り付けられたものの MIME タイプを確認
ここまでのところを実際に確認してみましょう。
<textarea></textarea>
<script>
document.querySelector('textarea')
.addEventListener('paste', ({ clipboardData }) => {
Array.from(clipboardData.items).forEach((item) => {
console.log(item.type);
});
});
</script>ブラウザで開いて <textarea> に様々なものをペーストしてみてください。コンソールで貼り付けたものの MIME タイプが確認できると思います。
なお、先ほど clipboardData.items は DataTransferItem オブジェクトの配列ライクと説明しましたが、インデックス番号の添え字で要素にアクセスしたりはできるのですが、forEach() のような Array.prototype が提供しているメソッドは使えません。従ってここでは Array.from() で一旦 Array にしてから forEach() でループしています。
貼り付けられたコンテンツを取得
ではいよいよペーストされた内容を取り出していきましょう。
先ほどは DataTransferItem.type で MIME タイプを確認しましたがが、別途 DataTransferItem.kind というプロパティーがあり、string か file のどちらかが入ります。DataTransferItem.kind が string か file によって利用するメソッドが異なってきます。
文字列として取得
DataTransferItem.kind が string の場合は、DataTransferItem.getAsString() メソッドを使用します。
DataTransferItem.getAsString()メソッドは、項目のkindが プレーン Unicode 文字列 (すなわちkindがstring)である場合に、ドラッグデータ項目の文字列データを引数に指定してコールバックを呼び出すメソッドです。
** 構文 **// アロー関数 getAsString((data) => { /* … */ } ) // コールバック関数 getAsString(callbackFn) // インラインコールバック関数 getAsString(function(data) { /* … */ })
やってみましょう。
<textarea></textarea>
<script>
document.querySelector('textarea')
.addEventListener('paste', ({ clipboardData }) => {
Array.from(clipboardData.items).forEach(item => {
if (item.kind === 'string' && item.type === 'text/html') {
item.getAsString((html) => console.log(html));
}
});
});
</script>ブラウザで開いて <textarea> に Web ページをコピーして貼り付けてみてください。<textarea> にはプレーンテキストしか貼り付けられていませんが、ブラウザ開発者ツールのコンソールには HTML タグ込みで出力されています。
HTML 構造ごと貼り付けたデータを取得できれば以下のようにスクレイピングの要領で構造を解析して必要なデータを取得するといったことも可能です。
// 略
items.getAsString((html) => {
const div = document.createElement('div');
div.innerHTML = html;
const data = div.querySelector('#...').innerText;
})
// 略
見たところインラインスクリプト等は落とされているように見えますが、クロスサイトスクリプティングにならないように貼り付けられた HTML をそのままレンダリングすることは避けた方がよいでしょう。
ファイルとして取得
DataTransferItem.kind が file の場合は、DataTransferItem.getAsFile() で File(File - Web API | MDN)オブジェクトとして取得します。
アイテムがファイルの場合、
DataTransferItem.getAsFile()メソッドはドラッグデータ項目の File オブジェクトを返します。項目がファイルでない場合、このメソッドはnullを返します。
構文getAsFile()返値
File
ドラッグデータ項目がファイルである場合、Fileオブジェクトが返されます。それ以外の場合はnullが返されます。
- DataTransferItem.getAsFile() - Web API | MDN
getAsString() がコールバックスタイルなのに対して getAsFile() は普通に返値として File オブジェクトが返ってきます。
こちらもやってみます。<img> タグを追加し、画像をコピーするとその画像を img 要素に表示させるようにします。
<textarea></textarea>
<img />
<script>
document.querySelector('textarea')
.addEventListener('paste', ({ clipboardData }) => {
Array.from(clipboardData.items).forEach(item => {
if (item.kind === 'file' && item.type === 'image/png') {
const file = item.getAsFile();
document.querySelector('img').src = URL.createObjectURL(file);
}
});
});
</script>ブラウザで開いて <textarea> に PNG 形式の画像を貼りつけてみてください。<img> 要素に表示されると思います。
DataTransferItem.getAsFile() は File オブジェクトで返ってくるので FileReader で読み取ったりと使い道は色々考えられると思います。
あとがき
paste イベントを使って様々なコンテンツを張り付ける方法を紹介しました。クリップボード内の更新とかをしようとするとクリップボード API を使うしかないですが、張り付けたデータをあれこれしたいだけであれば、paste イベントを使ったほうが簡単に扱えるのでよいなと思いました。