LoginSignup
1
0

More than 1 year has passed since last update.

[JavaScript]リアルタイム反映されるアップロード画像の拡張子変換が意外とめんどかった

Last updated at Posted at 2022-05-20

TL;DR

  • <input type='file'>で受け取ったfileの拡張子を.jpg→.pngの変更って簡単にできない
  • GUIなら簡単なのに…
  • フロント制御だけならHTMLのタグがあるが。。。

はじめに

やりたいこと

  • フロントでjpg, jpeg, pngを受け取れる<input type='file'>を作成
  • 受け取ったfileをサーバーに送信する際はpngに変換して、base_64文字列でリクエストしたい
    • 生fileでリクエストするとバリデーション内容膨大、ローカルストレージに保存される無駄、jpeg exifの考慮(画像の回転情報補正処理)があるのでかなり避けたい…

悩み

  • JSでサクッとできる方法がない…
  • GUIならサクッとできるのに…
    • 拡張子を.jpgから.pngにするだけ

仕方なしの対応

  • <input type='file'>で取得したファイルデータをblobに変換
  • canvasからblobへの変換はデフォルトでpngになる

補足

  • blobとは

  • JSにおけるblob

流れ

<input type='file'>で選択されたFileオブジェクト取得
②Fileオブジェクトで取得した画像データをFielReader APIを使ってImageオブジェクトに変換
③②のImageオブジェクトをCanvasに転写
④CanvasをBlobに変換
⑤BlobはデフォルトでmimeTypeがpngなので、ここからdataUrl(=base64)で取得

ブラウザ

初回画面

image.png

画像選択後

image.png

ソースコード

  • 「めんどくさいことしてんな」というイメージだけ捉えてもらえば大丈夫です。
<!-- 「保存する」ボタンはコンポーネント外で実装 -->
<template>
  <div>
    <div>
      <input
        type="file"
        @change="uploadFile"
      >
      <!-- サーバーにリクエストする値 -->
      <input
        type="hidden"
        :name="inputName"
        :value="urlBase64"
      >
      <!-- 初回描画時の画像 -->
      <div v-if="urlBase64 === ''">
        <img
          v-lazy="url"
        >
      </div>
      <!-- ファイル選択後の画像 -->
      <div v-show="urlBase64">
        <canvas
          :id="canvasId"
        />
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    url: {
      type: String,
      default: '',
    },
    canvasId: {
      type: String,
      default: '',
    },
    inputName: {
      type: String,
      default: 'url_base64',
    },
  },
  data () {
    return {
      urlBase64: '',
    }
  },
  methods: {
    uploadFile(event) {
      // onload内でVueComponentインスタンスを参照するために変数に代入
      const vm = this;
      // canvasからblob, base64へ変換
      const file = event.currentTarget.files[0];

      const readerForFile = new FileReader();
      readerForFile.onload = function() {
        // ファイルが読み込まれたあとの処理
        const image = new Image();
        image.onload = async function() {
          // imageを転写するcanvasの取得、加工
          const canvas = document.getElementById(vm.canvasId);

          canvas.width = image.width;
          canvas.height = image.height;

          // canvas(CanvasRenderingContext2D)に画像を転写
          const context = canvas.getContext("2d");
          context.drawImage(image, 0, 0);

          // canvas -> blobの変換
          const blob = await vm.canvasToBlob(canvas);
          // blob -> base64の変換してプロパティに代入
          vm.urlBase64 = await vm.readAsDataURL(blob);
          console.log(vm.urlBase64);
        }
        image.onerror = function() {
          alert('ファイルはjpg, jpeg, pngしか選べません。');
        }
        // 画像がimageタグに読み込み->image.onloadイベント発火
        image.src = readerForFile.result;
      }
      readerForFile.onerror = function() {
        alert('ファイルを読み込めませんでした。');
      }
      // Fileオブジェクトを読み込む->readerForFile.onloadイベント発火
      readerForFile.readAsDataURL(file);
    },
    canvasToBlob(canvas) {
      return new Promise((resolve) => {
        // mimtypeを分かりやすいように明示
        canvas.toBlob(resolve, 'image/png');
      });
    },
    readAsDataURL(blob) {
      return new Promise((resolve, reject) => {
        const readerForBlob = new FileReader();
        readerForBlob.onloadend = () => {
          resolve(readerForBlob.result);
        };
        readerForBlob.onerror = () => {
          reject(readerForBlob.error);
        };
        readerForBlob.readAsDataURL(blob);
      });
    },
  },
}
</script>

参考

所見

「サクッと拡張子変更したい」なんてよくある要件だよなと思いつつ、ライブラリないか探しましたが意外となかった…

結構「リアルタイム反映」と「pngへの変換」をごっちゃになっている可能性も否めないのですが、

「リアルタイム反映」という要件でcanvusに変換する必要がある
→どうせcanvas取得できるならそっからblobに変換しちゃったほうがいいや

という思考の流れです。

「jpgからpngの変換はおまえが思っているより簡単なことではない」

「こんなことせずとももっと簡単にできる」

などご助言あれば幸いです…

1
0
1

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
1
0