画像アップロードは案外重い
ブラウザからサーバへ画像をアップロードする際、画像のサイズに注意を払う必要があります。最近のスマホカメラの性能は凄く、iPhone 6S以降のiPhoneで撮った写真のサイズは 4032 x 3024
もあります。
このサイズのままアップロードすることは、もちろん不可能というわけではないのですが、上りネットワーク通信が遅い環境では結構な時間がかかったり、通信量がかかったりと色々な課題があります。
そこで、ブラウザ側で画像サイズを縮小してからアップロードできるようにしようと思います。アップロード時間を短縮できたり、通信量を抑えられたり、サーバ側の容量消費を抑えられたりと様々なメリットがあります。
画像縮小の流れ
ブラウザで画像を縮小する流れは以下のようになります。
- ファイルを選択し、ブラウザで画像を読み込む
- 縮小後の画像サイズを計算して
canvas
に描画する canvas
から縮小済み画像をbase64として取り出す- base64を画像データに変換する
基本的には以上です。
あとは、この画像データをajax等でアップロードできるようにすればOKなのですが、この部分は今回の記事では扱いません。
要素を取得するところでjQueryを使っていますが、別に素のJavaScriptを使っていただいても問題はありません。
ソースコード
以下のhtmlを適当な名前で保存してください。ここでは index.html
とします。
index.html
<!-- ファイル選択ボタン --> <input type="file" accept="image/*"> <!-- アップロードボタン --> <button id="upload">アップロード</button> <!-- 縮小画像の表示領域 --> <canvas id="canvas" width="0" height="0"></canvas> <!-- 以下、JavaScript --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script type="text/javascript"> $(function() { var file = null; // 選択ファイルが格納される変数 var blob = null; // 画像(BLOBデータ)が格納される変数 const THUMBNAIL_MAX_WIDTH = 500; // 画像がヨコ長の場合、横サイズがこの値になるように縮小される const THUMBNAIL_MAX_HEIGHT = 500; // 画像がタテ長の場合、縦サイズがこの値になるように縮小される // ファイルが選択されたら実行される関数 $('input[type=file]').change(function() { // ファイルを取得する file = $(this).prop('files')[0]; // 選択されたファイルが画像かどうか判定する // ここでは、jpeg形式とpng形式のみを画像をみなす if (file.type != 'image/jpeg' && file.type != 'image/png') { // 画像でない場合は何もせず終了する file = null; blob = null; return; } // 画像をリサイズする var image = new Image(); var reader = new FileReader(); reader.onload = function(e) { image.onload = function() { // 縮小後のサイズを計算する var width, height; if(image.width > image.height){ // ヨコ長の画像は横サイズを定数にあわせる var ratio = image.height/image.width; width = THUMBNAIL_MAX_WIDTH; height = THUMBNAIL_MAX_WIDTH * ratio; } else { // タテ長の画像は縦のサイズを定数にあわせる var ratio = image.width/image.height; width = THUMBNAIL_MAX_HEIGHT * ratio; height = THUMBNAIL_MAX_HEIGHT; } // 縮小画像を描画するcanvasのサイズを上で算出した値に変更する var canvas = $('#canvas') .attr('width', width) .attr('height', height); var ctx = canvas[0].getContext('2d'); // canvasに既に描画されている画像があればそれを消す ctx.clearRect(0,0,width,height); // canvasに縮小画像を描画する ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height ); // canvasから画像をbase64として取得する var base64 = canvas.get(0).toDataURL('image/jpeg'); // base64から画像データを作成する var barr, bin, i, len; bin = atob(base64.split('base64,')[1]); len = bin.length; barr = new Uint8Array(len); i = 0; while (i < len) { barr[i] = bin.charCodeAt(i); i++; } blob = new Blob([barr], {type: 'image/jpeg'}); } image.src = e.target.result; } reader.readAsDataURL(file); }); // アップロードボタンがクリックされたら実行される関数 $('#upload').click(function(){ // ファイルが指定されていなければ何も起こらない if(!file || !blob) { return; } // 送信するフォームデータを作成する var name, fd = new FormData(); // 先ほど作った縮小済画像データを添付する fd.append('file', blob); // ajax でアップロード $.ajax({ url: "http://exapmle.com", // 送信先のURL type: 'POST', dataType: 'json', data: fd, processData: false, contentType: false }) .done(function( data, textStatus, jqXHR ) { // 送信成功 }) .fail(function( jqXHR, textStatus, errorThrown ) { // 送信失敗 }); }); }); </script>
上から順に簡単に解説していきます。
var file = null; // 選択ファイルが格納される変数 var blob = null; // 画像(BLOBデータ)が格納される変数 const THUMBNAIL_MAX_WIDTH = 500; // 画像がヨコ長の場合、横サイズがこの値になるように縮小される const THUMBNAIL_MAX_HEIGHT = 500; // 画像がタテ長の場合、縦サイズがこの値になるように縮小される
コメントに書いてある通りですが、ここでグローバルな変数を定義しています。 THUMBNAIL_MAX_WIDTH
と THUMBNAIL_MAX_HEIGHT
はそれぞれ、縮小後の画像サイズの最大横pxと最大縦pxです。ここでは 500
としていますが、任意の数字で構いません。
元画像が横長の場合、横サイズを 500px
にします。元画像が縦長の場合、縦サイズを 500px
にします。なお、縦横比は保たれます。
次に、この部分です。
// 選択されたファイルが画像かどうか判定する
// ここでは、jpeg形式とpng形式のみを画像をみなす
if (file.type != 'image/jpeg' && file.type != 'image/png') {
// 画像でない場合は何もせず終了する
file = null;
blob = null;
return;
}
ここでは、選択されたファイルが jpeg形式とpng形式のいずれでもない場合、何も処理をしないようにしています。扱いたい画像形式を増やしたい場合は、この条件式を変えてください。
次にこの部分です。
var image = new Image(); var reader = new FileReader(); reader.onload = function(e) { image.onload = function() { .....
ここは若干トリッキーなのですが、ファイルのロードが完了した時と、画像のロードが完了した時の、それぞれのコールバック関数を書いています。おまじないと思ってもらって大丈夫です。
次にこの部分です。
// canvasに既に描画されている画像があればそれを消す ctx.clearRect(0,0,width,height); // canvasに縮小画像を描画する ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height );
ここではcanvasに画像を描画しています。引数の指定の仕方はこちらのサイト様で詳しく解説されています。 drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)-Canvasリファレンス
最後にこの部分。
// canvasから画像をbase64として取得する var base64 = canvas.get(0).toDataURL('image/jpeg'); // base64から画像データを作成する var barr, bin, i, len; bin = atob(base64.split('base64,')[1]); len = bin.length; barr = new Uint8Array(len); i = 0; while (i < len) { barr[i] = bin.charCodeAt(i); i++; } blob = new Blob([barr], {type: 'image/jpeg'});
ここでは、まずcanvasからbase64としてデータを取り出し、次にbase64を画像データに変換しています。 ここでは、jpeg画像を作るために type
を 'image/jpeg'
としています。
デモンストレーション
では index.html
を開いてみましょう。
こんな画面が表示されます。
ファイルを選択してみます。まずは横長の画像を選択してみました。
画像が表示されました。画像サイズを測ってみると、たしかに横幅が 500px
になっていました。
次に縦長の画像を選択してみます。
今度は縦サイズが 500px
になっています。
この状態でアップロードボタンを押せば、この縮小画像のアップロードが始まります。 ただし、今回のコードでは、アップロード先のURLはダミーになっていますので、実際にどこかにアップロードされることはありません。
以上、ブラウザで画像を縮小してサーバにアップロードする方法をまとめました。良い記事だと思っていただいた方は、以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリック、Twitterのフォローをお願いします!