この記事について
画像アップロードを作ろうとした際、サーバ側の負荷(主にメモリ)を減らす方法として、
クライアントでリサイズ済みの画像を送信する選択を取りました。
サーバに送信する画像形式の選択肢として、
- Base64
- BLOB
が調べた中で出てきました。
Base64は元データよりデータサイズが33%増えるため今回は扱いません。
デコードする処理もサーバ側へ実装する必要があり面倒なのでクライアントで全部やっちゃいます。
容量削減も目標であるためBLOBでサーバに画像データを送ります。
処理の流れ
- JavaScriptで指定したサイズで縦横比を維持したままリサイズし、かつセンタリングされた状態でcanvasに描写
- canvasからBLOB型に変換
- BLOBを
FormData
につめてPOST
する
以下コードと簡単な解説
コード自体は難しいものではないです。
ファイル取得 -> キャンバス描写
const imagePreviewBox = document.getElementById('preview')
const MAX_SIZE = 256
// ファイルが選択されたら
$('input[type=file]').change(function () {
let canvasList = new Array()
// 画像をリサイズする
for (i = 0; i < this.files.length; i++)
let image = new Image();
let reader = new FileReader();
let file = this.files[i];
let canvas = document.createElement('canvas')
reader.onload = function (e) {
image.onload = function () {
let width,
height,
marginTop = 0,
marginLeft = 0;
if (image.width > image.height) {
// 横長の画像は横のサイズを指定値にあわせる
let ratio = image.height / image.width;
width = MAX_SIZE;
height = MAX_SIZE * ratio;
marginTop = (MAX_SIZE - height) / 2
} else {
// 縦長の画像は縦のサイズを指定値にあわせる
let ratio = image.width / image.height;
width = MAX_SIZE * ratio;
height = MAX_SIZE;
marginLeft = (MAX_SIZE - width) / 2
}
canvas.setAttribute('width', MAX_SIZE)
canvas.setAttribute('height', MAX_SIZE)
imagePreviewBox.appendChild(canvas)
let ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0, image.width, image.height, marginLeft, marginTop, width, height)
canvasList.push(canvas)
}
image.src = e.target.result;
}
reader.readAsDataURL(file);
}
});
説明するほどでもないですが。
input type=file multple
で複数選択されたファイルをforで回して処理していきます。
Image
とFileReader
インスタンスは選択されたファイルの数だけ必要になるためforの内側で生成します。
if (image.width > image.height)
のifブロックで縦横比を維持するための高さ、幅、センタリングするためのtop,leftのマージンを計算。
計算したものをctx.drawImage
で描写
※コード内でcanvas要素をDOMへappendしていますが、canvasはメモリで保持しておけばcanvas->Blob変換は可能なのでDOMとして書きだす必要はありません。
canvas -> BLOB変換 -> FormDataに設定
toBlob(canvas) {
let base64 = canvas.toDataURL('image/png'),
bin = atob(base64.replace(/^.*,/, '')),
buffer = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) {
buffer[i] = bin.charCodeAt(i);
}
return new Blob([buffer.buffer], { type: 'image/png' })
}
let fd = new FormData(document.getElementById('upload-form'))
this.canvasList.forEach((canvas) => {
fd.append("何かキーとなる名前", toBlob(canvas))
})
ajaxでpost
let ajaxParam = {
url: 'http://example.com/foo',
method: 'POST',
data: fd,
dataType: "text",
cache: false,
contentType: false,
processData: false
}
$.ajax(ajaxParam)
.done()
.fail()
これでマルチパートで送信されます。
詰まったところ
Q:FormDataに詰めたデータをconsole.log(fd)
しても詰めたデータ見れないじゃん。
A:
fd.get('キー')
// or
for(item of formData){
console.log(item);
}
参考
https://qiita.com/0829/items/a8c98c8f53b2e821ac94
http://matz.hatenablog.jp/entry/2017/03/26/014615