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> に貼り付けてみます。ブラウザ開発者ツールのコンソールを見てみると ClipBoardEventClipboardEvent - Web API | MDN) が表示されています。

ClipboardEvent は他のイベントにはない clipboardDataClipboardEvent: clipboardData プロパティ - Web API | MDN)というプロパティーを持っています。

clipboardData プロパティーは DataTransferDataTransfer - Web API | MDN)オブジェクトとなっていて、items というプロパティーに貼り付けされた項目が DataTransferItemDataTransferItem - Web API | MDN)オブジェクトとして配列ライクに格納されています。

DataTransferItem は貼り付けられた内容の形式(通常は MIME タイプ)単位に作成されるので注意です。例えば Web ページをコピーして貼り付けた場合は、text/plaintext/html の 2 つの MIME タイプを有するため、clipboardData.items.length2 になります。また、画像も Word や PowerPoint から貼り付けると、image/svg+xmlimage/png のように 2 つの MIME タイプを持つため、同様に clipboardData.items.length2 になります。

貼り付けられたものの MIME タイプを確認

ここまでのところを実際に確認してみましょう。

<textarea></textarea>
<script>
  document.querySelector('textarea')
    .addEventListener('paste', ({ clipboardData }) => {
      Array.from(clipboardData.items).forEach((item) => {
        console.log(item.type);
      });
    });
</script>

ブラウザで開いて <textarea> に様々なものをペーストしてみてください。コンソールで貼り付けたものの MIME タイプが確認できると思います。

なお、先ほど clipboardData.itemsDataTransferItem オブジェクトの配列ライクと説明しましたが、インデックス番号の添え字で要素にアクセスしたりはできるのですが、forEach() のような Array.prototype が提供しているメソッドは使えません。従ってここでは Array.from() で一旦 Array にしてから forEach() でループしています。

貼り付けられたコンテンツを取得

ではいよいよペーストされた内容を取り出していきましょう。

先ほどは DataTransferItem.type で MIME タイプを確認しましたがが、別途 DataTransferItem.kind というプロパティーがあり、stringfile のどちらかが入ります。DataTransferItem.kindstringfile によって利用するメソッドが異なってきます。

文字列として取得

DataTransferItem.kindstring の場合は、DataTransferItem.getAsString() メソッドを使用します。

DataTransferItem.getAsString() メソッドは、項目の kind が プレーン Unicode 文字列 (すなわち kindstring)である場合に、ドラッグデータ項目の文字列データを引数に指定してコールバックを呼び出すメソッドです。
** 構文 **

// アロー関数
getAsString((data) => { /* … */ } )

// コールバック関数
getAsString(callbackFn)

// インラインコールバック関数
getAsString(function(data) { /* … */ })

DataTransferItem.getAsString() - Web API | MDN

やってみましょう。

<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.kindfile の場合は、DataTransferItem.getAsFile()FileFile - Web API | MDN)オブジェクトとして取得します。

アイテムがファイルの場合、DataTransferItem.getAsFile() メソッドはドラッグデータ項目の File オブジェクトを返します。項目がファイルでない場合、このメソッドは null を返します。
構文

getAsFile()

返値

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 イベントを使ったほうが簡単に扱えるのでよいなと思いました。