LoginSignup
2
2

More than 5 years have passed since last update.

ブラウザで(複数)選択されたローカル画像をリサイズしてPOST

Last updated at Posted at 2018-06-28

この記事について

画像アップロードを作ろうとした際、サーバ側の負荷(主にメモリ)を減らす方法として、
クライアントでリサイズ済みの画像を送信する選択を取りました。

サーバに送信する画像形式の選択肢として、

  • Base64
  • BLOB

が調べた中で出てきました。

Base64は元データよりデータサイズが33%増えるため今回は扱いません。
デコードする処理もサーバ側へ実装する必要があり面倒なのでクライアントで全部やっちゃいます。
容量削減も目標であるためBLOBでサーバに画像データを送ります。

処理の流れ

  1. JavaScriptで指定したサイズで縦横比を維持したままリサイズし、かつセンタリングされた状態でcanvasに描写
  2. canvasからBLOB型に変換
  3. 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で回して処理していきます。
ImageFileReaderインスタンスは選択されたファイルの数だけ必要になるため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

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2