【JavaScript】クリップボードに文字列や画像データをコピーする方法

さまざまなウェブサイトでクリップボードにテキストをコピーする機能が使われています。しかし、クリップボードにはプレーンテキストだけでなくスタイル付きのテキストや画像データも書き込むことができます。また、クリップボードにはプレーンテキストとスタイル付きテキストといった具合に複数の種類のデータを同時にコピーすることも可能です。

今回は、JavaScript を使ってプレーンテキスト、スタイル付きテキスト、画像データをクリップボードにコピーする方法を詳しく解説します。

プレーンテキストをクリップボードにコピーする最もシンプルな方法

ウェブサイトでのクリップボードの操作の中で最もポピュラーなのは、プレーンテキストのコピーです。モダンブラウザーであれば、次のコードで簡単に実現することができます。

<button type="button" id="btn">Copy</button>
document.getElementById('btn').addEventListener('click', async () => {
  await navigator.clipboard.writeText('Hello World');
  window.alert('クリップボードにコピーしました。');
});

navigator.clipboardwriteText() メソッドには、クリップボードにコピーしたいテキストを指定します。ここでは "Hello World" を指定しています。このメソッドは非同期処理のため Promise オブジェクトを返します。上記サンプルコードでは async の関数の中で writeText() メソッドを呼び出していますので、await を使っています。

Promise っぽく次のように書くこともできます。

document.getElementById('btn').addEventListener('click', () => {
  navigator.clipboard.writeText('Hello World').then(() => {
    window.alert('クリップボードにコピーしました。');
  });
});

writeText() メソッドによってクリップボードにコピーされたテキストは、たとえばメモ帳や Word といった別のデスクトップアプリケーション上でペーストすることができます。

Internet Explorer 11 でも使える execCommand() メソッド

テキストをクリップボードにコピーする方法として、document オブジェクトの execCommand() メソッドを使う方法も良く知られています。Internet Explorer 11 でも動作するため、今なお多くのウェブサイトで使われています。次のようなケースで使われることが多いのではないでしょうか。

<input type="text" id="msg" value="Web Frontend Ninja">
<button type="button" id="btn">Copy</button>
document.getElementById('btn').addEventListener('click', function () {
  // テキストボックスの内容を選択状態にする
  const input = document.getElementById('msg');
  input.select();
  // 選択状態のテキストをコピーする
  document.execCommand('copy');
  window.alert('クリップボードにコピーしました。');
});

このサンプルは、Copy ボタンを押すと、テキストボックスの内容をクリップボードにコピーします。とても良く見かけるケースではないでしょうか。コピーが完了すると、次のような状態になります。

このサンプルコードでは input 要素のテキストボックスを使いましたが、通常のページ上のフレーズでもコピーが可能です。次のサンプルは span 要素のテキストをクリップボードにコピーします。

<p>こんにちわ。<span id="sitename">Web Frontend Ninja</span> です。</h2>
<button type="button" id="btn">Copy</button>
document.getElementById('btn').addEventListener('click', function () {
  // span 要素の内容を選択状態にする
  const selection = document.getSelection();
  const range = document.createRange();
  const span = document.getElementById('sitename');
  range.setStart(span, 0);
  range.setEnd(span, span.childNodes.length);
  selection.addRange(range);

  // 選択状態のテキストをコピーする
  document.execCommand('copy');
  window.alert('クリップボードにコピーしました。');
});

コピーが完了すると、次のような状態になります。

以上のいずれのサンプルも、該当のテキストは選択状態になる点に注目してください。お気づきの通り、document.execCommand('copy') は、ユーザーの代わりにキーボードで Ctrl + C を押したようなものです。テキストをクリップボードにコピーしたければ、事前にテキストを選択状態にしなければいけません。

一方で、navigator.clipboard.writeText() メソッドは、テキストの選択状態を作り出すことなく、任意の文字列をクリップボードにダイレクトに書き込みます。

もちろん、 document.execCommand('copy') でも、 navigator.clipboard.writeText() メソッドのようなことはできます。

document.getElementById('btn').addEventListener('click', function () {
  // input 要素を生成して任意の文字列をセットする
  let input = document.createElement('input');
  input.value = 'Web Frontend Ninja';
  // input 要素を body 要素に追加する
  document.body.appendChild(input);
  // input 要素の内容を選択状態にする
  input.select();
  // 選択状態のテキストをコピーする
  document.execCommand('copy');
  // input 要素を body 要素から削除する
  document.body.removeChild(input);
  window.alert('クリップボードにコピーしました。');
});

かなり無理をしてますね。任意の文字列をクリップボードにコピーするなら、navigator.clipboard.writeText() のほうが圧倒的にシンプルです。

なお、現在、 execCommand() メソッドは非推奨となっています。Internet Explorer 11 をサポートしなければいけない状況でない限り、execCommand() メソッドの利用は避けましょう。

スタイル付きテキストをコピーする

前述のサンプルではスタイルを持たないプレーンテキストをコピーする方法を説明してきました。しかし、クリップボードにはスタイルを保持したままテキストをコピーできることは、みなさんもご存知の通りです。実は、JavaScript からでも、スタイル付きのテキストをクリップボードに差し込むことが可能です。

ここからは Chrome や Edge といった Chromium ベースのブラウザーと Safari のみが対象です。残念ながら、Firefox ではデフォルトでは動作しません。では、まずコードをご覧ください。

document.getElementById('btn').addEventListener('click', async () => {
  const html = 'Web <b>Frontend</b> <span style="color:red;">Ninja</span>';
  const item = new ClipboardItem({
    'text/html': new Blob([html], { type: 'text/html' })
  });
  await navigator.clipboard.write([item]);
  window.alert('クリップボードにコピーしました。');
});

この記事の冒頭のサンプルでは、navigator.clipboardwriteText() メソッドを使いましたが、ここでは write() メソッドを使っています。write() メソッドは引数に Blob オブジェクトを受け取ります。その Blob オブジェクトを生成する箇所に注目してください。HTML のコードを Blob オブジェクトにしています。

HTML コードを text/html としてコピーすることで、自由にスタイリングしたテキストをクリップボードにコピーすることが可能になります。実際にこのコードを実行して HTML コードをクリップボードにコピーした後、Word に張り付けると次のようになります。

HTML とプレーンテキストの容量を同時にコピーする

前述の例ではスタイル付きのテキストを HTML コードでクリップボードにコピーしましたが、実はちょっとした問題があります。メモ帳など、プレーンテキストしか受け付けないテキストエディタではペーストできないのです。実は、テキストエディタに限らず、プレーンテキストしか受け付けないところでは何もペーストされません。

通常、スタイル付きのテキストを Ctrl + C でコピーしてメモ帳にペーストしても、スタイルなしのプレーンテキストとしてペーストされます。そして、利用者はそれが当たり前と思うでしょう。ではどうすればよいのでしょうか。

実は、クリップボードには同時に複数のフォーマットのデータをコピーできるのです。JavaScript でクリップボードを操作する場合は、そういう点も考慮が必要になります。では、次のコードをご覧ください。

document.getElementById('btn').addEventListener('click', async () => {
  const text = 'Web Frontend Ninja';
  const html = 'Web <b>Frontend</b> <span style="color:red;">Ninja</span>';
  const item = new ClipboardItem({
    'text/plain': new Blob([text], { type: 'text/plain' }),
    'text/html': new Blob([html], { type: 'text/html' })
  });
  await navigator.clipboard.write([item]);
  window.alert('クリップボードにコピーしました。');
});

ClipboardItem() コンストラクタから ClipboardItem オブジェクト(変数 item)を生成する際に、text/plainBlob もセットしています。こうすることによって、ごく一般的なクリップボードコピーを再現することができます。

表示画像のデータをコピーする

JavaScript から画像データもクリップボードにコピーすることが可能です。ただし、画像データを扱えるのは、現時点では Chrome や Edge などの Chromium ベースのブラウザーのみです。

前述のサンプルからなんとなくどうすれば良いかが想像できますね。画像データを表す Blob オブジェクトを用意すればよいのです。その際の MIME タイプは image/png です。

次のサンプルは、HTML 上にある img 要素の画像データをクリップボードにコピーします。どうやって img 要素の画像データを表す Blob オブジェクトを生成するのかについて注目してください。

<p><button type="button" id="btn">Copy</button></p>
<p><img src="pic.jpg" id="pic" width="400" height="225" alt=""></p>
document.getElementById('btn').addEventListener('click', () => {
  // canvas 要素を新規に生成して img 要素の画像データを書き込む
  const img = document.getElementById('pic');
  const canvas = document.createElement('canvas');
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);

  // Canvas から Blob オブジェクトを生成
  canvas.toBlob(async (blob) => {
    // 画像データをクリップボードに書き込む
    const item = new ClipboardItem({
      'image/png': blob
    });
    await navigator.clipboard.write([item]);
    window.alert('クリップボードにコピーしました。');
  });
});

img 要素から直接的に Blob オブジェクトを作る方法はありません。そのため、canvas 要素を使います。

まず、img 要素に組み込まれた画像の大きさと同じ大きさの canvas 要素を内部的に生成します。その際に、img 要素のオブジェクトの naturalWidthnaturalHeight プロパティを使っている点に注意してください。ページ上に表示されている画像のサイズと、実際の画像のサイズが同じとは限りません。そのため、本来の画像のサイズを表す naturalWidthnaturalHeight プロパティから画像のサイズを割り出しています。

画像と同じサイズの canvas 要素を生成したら、そこに画像を貼り付けます。そして、canvas 要素のオブジェクトの toBlob() メソッドを使って、Blob オブジェクトを生成します。

このコードでクリップボードに画像データをコピーした後、Word などの画像データを受け付けるアプリケーション上でペーストすれば、期待通りに該当の画像が張り付けられます。

残念ながら、筆者が試したところ、Safari では画像データをうまく扱えませんでした。ネット上には Safari 向けに少しコードを書き換えれば機能するという情報が見られますが、筆者が試しても動作しませんでした。新たな情報が入り次第、この記事をアップデートする予定です。

画像ファイルをダウンロードして画像データをコピーする

先ほどのサンプルでは img 要素の画像をコピーしましが、fetchXMLHttpRequest でダウンロードした画像でもうまく動作します。ただし、PNG ファイルでないと動作しませんので注意してください。

document.getElementById('btn').addEventListener('click', async () => {
  const res = await fetch('pic.png');
  const blob = await res.blob()
  const item = new ClipboardItem({
    'image/png': blob
  });
  await navigator.clipboard.write([item]);
  window.alert('クリップボードにコピーしました。');
});

ブラウザーのサポート状況

これまで紹介してきた機能について、改めてブラウザーのサポート状況を整理します。

機能ChromeEdgeSafariFirefoxIE11
document.execCommand('copy')
navigator.clipboard.writeText() ×
navigator.clipboard.write()※1※2×

※1:Safari は公式に image/png をサポートしているとしていますが、筆者は動作が確認できませんでした。筆者の環境の問題の可能性もありますのでご了承ください。

※2:Firefox は navigator.clipboard を実験的に実装しています。フラグオプションを変更することで試すことが可能です。クリップボードに関するフラグオプションについては、記事「クリップボードの内容を読み取る方法」をご覧ください。

まとめ

ブラウザーのサポート状況を見ての通り、Internet Explorer 11 を除けば、どのブラウザーも navigator.clipboard.writeText() をサポートしています。もし Internet Explorer 11 を対象外にできるのであれば execCommand() メソッドを使うのはやめて、writeText() を使いましょう。

本記事での解説の通り、navigator.clipboard.write() を使えば、デスクトップアプリケーションと何ら違いのないクリップボード操作が可能になります。まだ Firefox と Safari に制約がありますが、早くどのモダンブラウザーでも同じ JavaScript コードで使えるようになると良いですね。

Chrome と Edge のような Chromium 系のブラウザーは画像データも扱えますが、現時点では image/png しかサポートしていません。そのほかの画像フォーマットにも対応してくれると、より便利になりそうですね。

今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share