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)で取得
ブラウザ
初回画面
画像選択後
ソースコード
- 「めんどくさいことしてんな」というイメージだけ捉えてもらえば大丈夫です。
<!-- 「保存する」ボタンはコンポーネント外で実装 -->
<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の変換はおまえが思っているより簡単なことではない」
「こんなことせずとももっと簡単にできる」
などご助言あれば幸いです…