Help us understand the problem. What is going on with this article?

【JavaScript】フォーム上などの画像ファイルをブラウザだけでリサイズ

JavaScriptで画像ファイルをリサイズしたい時があると思います。僕はありました。
単にFileを投入して変換するコードが意外となかったのでここに共有します。
この例では、リサイズ後の画像をブラウザからダウンロードの形で取得できるようにしました。

DEMO

See the Pen GRJRrPW by hiroism (@jukaism) on CodePen.

Canvasを利用します

Canvasは図を表示するための仕様ですが、機能が豊富なので画像加工にも利用できます。

  <body>
    <canvas style="width: 484px; height: 253px;" id="canvas"></canvas>
  </body>

画像をリサイズしたい幅と高さを指定しておきます。邪魔ならdisplay: none;も可。

コード全体

htmlファイルにしてブラウザで開けば動くコードです。
スクリプト内のresize(file)にFileを投入するほか、
添付フィールドを用意したので、ここから画像を選んでも動作します。
image.png

<html lang="ja">
  <body>
    <input type="file" id="image_zone">
    <br>
    <canvas style="width: 484px; height: 253px;" id="canvas"></canvas>
    <script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script> <!-- IEで必要。不要なら削除を -->
    <script>
      // image_zoneに新しいファイルがセットされた時、resizeを呼ぶ
      const imageZone = document.getElementById('image_zone')
      imageZone.addEventListener('change', resizePinnedImage, false)
      function resizePinnedImage(e) {
          const file = e.target.files[0]
          if (!file.type.match('image.*')) { return }
          resize(file)
      }
      // Canvasに画像をリサイズして貼り、終わったらブラウザからダウンロードっぽく取得
      function resize(file) {
        imageToCanvas(file).then(function (canvas) {
          // [5] キャンバスから画像を取得
          if (canvas.msToBlob) {
            window.navigator.msSaveBlob(canvas.msToBlob(), 'resized.png')
          } else {
            const a = document.createElement('a')
            a.href = canvas.toDataURL(file.type)
            a.download = 'resized.' + file.type.split('/')[1]
            a.click()
          }
        }).catch(function (error) {
          console.error(error)
        })
      }

      function imageToCanvas (imageFile) {
        // [1] Fileから画像データを得る
        return new Promise(function (resolve, reject) {
          readImage(imageFile).then(function (src) {
            loadImage(src).then(function (image) {
              // [2] Canvasの呼び出し
              const canvas = document.getElementById("canvas")
              const ctx = canvas.getContext('2d')
              // [3] 縮尺を計算
              const scale = Math.max((canvas.height / image.height), (canvas.width / image.width))
              // [4] 画像を切り取り、Canvasに展開して返す
              ctx.drawImage(
                // Canvasに貼り付ける画像
                image, 
                // 画像の切り取り開始位置(左上からの横距離、縦距離)
                (image.width - (canvas.width / scale)) / 2, (image.height - (canvas.height / scale)) / 2,
                // 画像の切り取り範囲(幅、高さ)
                canvas.width / scale, canvas.height / scale,
                // キャンバスの貼り付け開始位置(左上からの横距離、縦距離)
                0, 0,
                // 画像への貼り付け範囲(幅、高さ)
                canvas.width, canvas.height
              )
              resolve(canvas)
            }).catch(function (error) {
              reject(error)
            })
          }).catch(function (error) {
            reject(error)
          })
        })
      }

      // [1-A] Fileの中身を得る
      function readImage(image) {
        return new Promise(function (resolve, reject) {
          const reader = new FileReader()
          reader.onload = function () { resolve(reader.result) }
          reader.onerror = function (e) { reject(e) }
          reader.readAsDataURL(image)
        })
      }
      // [1-B] 空の画像データを作った後に中身を投入
      function loadImage(src) {
        return new Promise(function (resolve, reject) {
          const img = new Image()
          img.onload = function () { resolve(img) }
          img.onerror = function (e) { reject(e) }
          img.src = src
        })
      }
    </script>
  </body>
</html>

[1] FileをImageにする
FileReaderでFileの中身を取り出し、空のImageに投入します。
このあたりでPromiseを多用しているためIE用のPolyfillを入れています。

    <script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
      // ..略
      function imageToCanvas (imageFile) {
        // [1] Fileから画像データを得る
        return new Promise(function (resolve, reject) {
          readImage(imageFile).then(function (src) {
            loadImage(src).then(function (image) {
            // ここに画像データ(image)が渡ってくるので処理
            }).catch(function (error) {
              reject(error)
            })
          }).catch(function (error) {
            reject(error)
          })
        })
      }
      // [1-A] Fileの中身を得る
      function readImage(image) {
        return new Promise(function (resolve, reject) {
          const reader = new FileReader()
          reader.onload = function () { resolve(reader.result) }
          reader.onerror = function (e) { reject(e) }
          reader.readAsDataURL(image)
        })
      }
      // [1-B] 空の画像データを作った後に中身を投入
      function loadImage(src) {
        return new Promise(function (resolve, reject) {
          const img = new Image()
          img.onload = function () { resolve(img) }
          img.onerror = function (e) { reject(e) }
          img.src = src
        })
      }

[2] HTML上のCanvasの呼び出し
キャンバスと、その描画用のコンテキストを呼び出します。

              // [2] Canvasの呼び出し
              const canvas = document.getElementById("canvas")
              const ctx = canvas.getContext('2d')

[3] 縮尺の計算
アスペクト比を維持したいので、縦横どちらか大きい方の縮小率を採用します。

              // [3] 縮尺を計算
              const scale = Math.max((canvas.height / image.height), (canvas.width / image.width))

[4] 画像を切り取り、Canvasに展開
CanvasのメソッドdrawImageを使って画像を貼り付け。
この時画像の切り取り範囲とCanvasへの貼付け範囲を一気に指定する。混乱しがち。
本当はここで画像にしたかったが、ブラウザによってこの後のデータの取り出し方が違うのでここではCanvasを返す。

              // [4] 画像を切り取り、Canvasに展開して返す
              ctx.drawImage(
                // Canvasに貼り付ける画像
                image, 
                // 画像の切り取り開始位置(左上からの横距離、縦距離)
                (image.width - (canvas.width / scale)) / 2, (image.height - (canvas.height / scale)) / 2,
                // 画像の切り取り範囲(幅、高さ)
                canvas.width / scale, canvas.height / scale,
                // キャンバスの貼り付け開始位置(左上からの横距離、縦距離)
                0, 0,
                // 画像への貼り付け範囲(幅、高さ)
                canvas.width, canvas.height
              )
              resolve(canvas)

[5] Canvasから画像を取得
Canvasにリサイズ後の画像が表示され、Canvasの各種メソッドでDataURLやBlobが取得出来る。
サンプルではブラウザからダウンロードできるようにした。IEでもいけますがpng固定になるので注意。

          // [5] キャンバスから画像を取得
          if (canvas.msToBlob) { // IEの場合
            window.navigator.msSaveBlob(canvas.msToBlob(), 'resized.png')
          } else { // その他ブラウザ
            const a = document.createElement('a')
            a.href = canvas.toDataURL(file.type)
            a.download = 'resized.' + file.type.split('/')[1]
            a.click()
          }

以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした