JavaScriptでクリップボードの画像取得+リサイズ

(1/1)
Bluesky投稿用プログラムのフロントエンドで使うことを目的に、JavaScript だけでクリップボードにある画像を取得し、クライアント側でリサイズするプログラムを作みた。なので、サンプル・プログラムは画像を最大4つまで取り込んで、画面に表示できるようにしてある。また、表示した画像を削除するボタンも用意した。

(2024年12月6日)画像を指定サイズに縮小する機能追加,タイトル変更

目次

サンプル・プログラムの実行例

JavaScriptでクリップボードの画像を取得する

サンプル・プログラムのダウンロード

圧縮ファイルの内容
pasteImage.htmlサンプル・プログラム本体
pasteImage.html 更新履歴
バージョン 更新日 内容
1.1.0 2024/12/06 画像縮小処理--resizeImage()を追加
1.0.0 2024/11/30 初版

解説:初期値

pasteImage.html

  25: // 初期値(START) ==========================================================
  26: const TITLE = 'クリップボードの画像取得+リサイズ'// プログラム・タイトル
  27: const REFERENCE = 'https://www.pahoo.org/e-soul/webtech/js01/js01-17-01.shtm';
  28: const WIDTH  = 600;                             // 横幅(ピクセル)
  29: const MAX_IMAGES = 4;                           // 最大画像数
  30: const MAX_WIDTH = 1200;                         // 画像の最大幅(ピクセル)
  31: const MAX_HEIGHT = 630;                         // 画像の最大高(ピクセル)
  32: 
  33: // imgのリスト;MAX_IMAGESの数だけ用意すること
  34: const IMAGE_LIST = ['image1', 'image2', 'image3', 'image4'];
  35: 
  36: document.addEventListener('DOMContentLoaded', () => {
  37:     //タイトル等を表示する.
  38:     document.title = TITLE;
  39:     document.getElementById('title').innerHTML = TITLE + '&nbsp;<span style="font-size:small;">' + getLastModified() + '版</span>';
  40:     document.getElementById('reference').innerHTML =`
  41: ※参考サイト&nbsp;<a href="${REFERENCE}">${REFERENCE}</a>
  42: `;
  43: 
  44:     //スタイルシートをセットする.
  45:     document.getElementById('help').style.width  = WIDTH + 'px';
  46:     document.getElementById('help1').innerHTML = `画像は ${MAX_IMAGES}個までペーストできます.`;
  47:     let width = Math.floor(WIDTH / MAX_IMAGES);
  48:     IMAGE_LIST.forEach((value, index) => {
  49:         document.getElementById(value).style.width  = width + 'px';
  50:         document.getElementById(value).style.height  = 'auto';
  51:     });
  52: });
  53: // 初期値(END) ==========================================================

冒頭で述べたように、取り込み可能な画像数は、あらかじめ MAX_IMAGES に代入しておく。これらを別々のimgタグに表示する関係上、imgタグのid属性のリストを配列として IMAGE_LIST にあらかじめ代入しておくこと。

解説:画像を指定サイズに縮小

pasteImage.html

 130: /**
 131:  * 画像を指定サイズに縮小し,WebP画像に変換し,オブジェクトURL形式で返す
 132:  * @param   Object blob    画像データ(Blob形式)
 133:  * @param   Number width   リサイズ後の幅(ピクセル)
 134:  * @param   Number height  リサイズ後の高さ(ピクセル)
 135:  * @return  String オブジェクトURL形式
 136: */
 137: function resizeImage(blob, width, height) {
 138:     // 画像をロードするまで待つ
 139:     return new Promise((resolve, reject) => {
 140:         // 縮小倍率
 141:         let scale = 1.0;
 142:         
 143:         // 画像をロードする
 144:         const img = new Image();
 145:         const url = URL.createObjectURL(blob);
 146:         
 147:         img.onload = () => {
 148:             // 元の画像サイズ
 149:             const originalWidth  = img.width;
 150:             const originalHeight = img.height;
 151:         
 152:             // 縮小後のサイズ計算
 153:             if (originalWidth > width) {
 154:                 scale = width / originalWidth;
 155:             }
 156:             if (originalHeight > height * scale) {
 157:                 scale = height / originalHeight;
 158:             }
 159:             const newWidth  = originalWidth  * scale;
 160:             const newHeight = originalHeight * scale;
 161:             
 162:             // canvasを生成する
 163:             const canvas = document.createElement('canvas');
 164:             canvas.width  = newWidth;
 165:             canvas.height = newHeight;
 166:             
 167:             // canvasに描画する
 168:             const ctx = canvas.getContext('2d');
 169:             ctx.drawImage(img, 0, 0, newWidth, newHeight);
 170:             
 171:             // BlobのURLを解放する
 172:             URL.revokeObjectURL(url);
 173:             
 174:             // WebP画像をオブジェクトURL形式で返す
 175:             resolve(canvas.toDataURL('image/webp'));
 176:         };
 177:         
 178:         img.onerror = (err) => {
 179:             console.error('画像の読み込みに失敗しました:', err);
 180:             URL.revokeObjectURL(url);
 181:             reject(err);
 182:         };
 183: 
 184:         img.src = url;
 185:     });
 186: }

サーバへ画像データを送信するとき、サーバ負荷を抑えるためと、そもそもPOSTデータとしてアップロードできないような巨大画像がペーストされたときに備え、画像を指定サイズに縮小してから表示するようにする。
ユーザー関数 resizeImage は、Blob形式の画像データを受け取り、width, height より大きい場合には縮小する。縮小の有無に関わらず、画像データを小さくできる WebP形式に変換し、POSTでサーバに送信しやすいように オブジェクトURL形式として返す。

まず、Imageオブジェクトを用意し、Blob形式の画像データをロードする。ここでロード処理は非同期で進むので、本関数は Promise で return するようにする。
ロードした画像サイズを取得し、引数で指定したサイズより大きければ縮小率を計算し、変数 scale に代入する。指定サイズより小さければ、scale はデフォルトの1.0(倍)のままとなる。
画像の縮小は canvas を利用して行う。変換結果は、WebP形式の画像として取得する。

解説:クリップボードの画像を取得する

pasteImage.html

  91: // ctrl+V をクリックしたらクリップボードから画像を取得し画面に表示する
  92: document.addEventListener('paste', async (event) => {
  93:     // 格納できる要素(srcが空の要素)を探す
  94:     let flag = false
  95:     let imgElement = undefined;
  96:     for (const id of IMAGE_LIST) {
  97:         imgElement = document.getElementById(id);
  98:         if (imgElement && imgElement.src === '') {
  99:             flag = true;
 100:             break;
 101:         }
 102:     }
 103: 
 104:     try {
 105:         // クリップボードのデータを取得
 106:         const clipboardItems = await navigator.clipboard.read();
 107: 
 108:         for (const item of clipboardItems) {
 109:             // 画像データ (image/*) を探す
 110:             const imageType = item.types.find(type => type.startsWith('image/'));
 111:             if (imageType) {
 112:                 if (! flag) {
 113:                     document.getElementById('result').innerHTML = '情報:投稿できる画像は {$maxImages}個までです.';
 114:                     return;
 115:                 }
 116:                 const blob = await item.getType(imageType);
 117:                 // 画像を指定サイズに縮小してから表示
 118:                 imgElement.src = await resizeImage(blob, MAX_WIDTH, MAX_HEIGHT);
 119:                 // 画像を縮小しないで表示する
 120:                 // imgElement.src = URL.createObjectURL(blob);
 121:                 // 削除ボタンを作成して画像の右上に追加
 122:                 addDeleteButton(imgElement);
 123:             }
 124:         }
 125:     } catch (err) {
 126:         document.getElementById('result').innerHTML = 'エラー:' + err;
 127:     }
 128: });

まず、先ほど定義した IMAGE_LIST から格納できる要素、すなわちsrc属性が空の要素を探す。

次に、Clipboard.read メソッドを使ってクリップボードの内容を読み込み、その中から forループを使って画像データを取り出す。画像データは item.types が MIME形式で "image/*" になっている。
画像データが見つかったら、blob形式で読み込み、img.srcにURL形式で代入する。これで1つの画像を表示できた。

つづいて、画像の右上に削除ボタンを配置する。

pasteImage.html

  55: // 画像に削除ボタンを追加する
  56: function addDeleteButton (imgElement) {
  57:     const deleteButton = document.createElement('button');
  58:     deleteButton.textContent = '削除';
  59:     deleteButton.style.position = 'absolute';
  60:     deleteButton.style.top = '0';
  61:     deleteButton.style.right = '0';
  62:     deleteButton.style.backgroundColor = 'red';
  63:     deleteButton.style.color = 'white';
  64:     deleteButton.style.border = 'none';
  65:     deleteButton.style.padding = '5px 10px';
  66:     deleteButton.style.cursor = 'pointer';
  67:     deleteButton.style.fontSize = '12px';
  68:     deleteButton.style.zIndex = '10';
  69: 
  70:     // 画像の親要素に削除ボタンを追加
  71:     const imgContainer = document.createElement('div');
  72:     imgContainer.style.position = 'relative';
  73:     imgContainer.style.display = 'inline-block';
  74:     imgContainer.appendChild(imgElement);
  75:     imgContainer.appendChild(deleteButton);
  76: 
  77:     // 画像と削除ボタンをページに追加
  78:     document.getElementById('image-container').appendChild(imgContainer);
  79: 
  80:     // 削除ボタンのクリックイベントを設定
  81:     deleteButton.addEventListener('click', () => {
  82:         // src属性を削除し,画像を非表示にする
  83:         imgElement.removeAttribute('src');
  84:         // imgをコンテナに付け直す
  85:         document.getElementById('image-container').appendChild(imgElement);
  86:         // 画像と削除ボタンを削除する
  87:         imgContainer.remove();
  88:     });
  89: }

まず、buttonオブジェクトを生成し、ラベルやサイズを設定したら、画像と一緒にオブジェクト imgContainer にぶら下げる。
buttonオブジェクトがクリックされたら、画像のsrc属性を削除することで画像を非表示にするとともに、imgオブジェクトを imgContainer に付け直す。

参考サイト

(この項おわり)
header