画像アップロードの色々
https://gyazo.com/d623f525a497cc976018a17c7b2c7c96
ファイルのアップロードについて
APIでよく使われるJSON形式ではファイルをアップロードできない。
multipart/form-dataで送る必要がある
code:pug
//- 通常のform送信
form(
method="POST"
action="/api/upload"
enctype="multipart/form-data"
)
input(type="file" name="file")
button(type="submit") 送信
ちなみにcurlだと以下のように書く
code:shell
サーバー側の実装(Node.js)
サーバー側でmultipart/form-dataを受け取るにはNode.jsの場合はmulterを使う。
code:typescript
import Express from 'express';
import multer from 'multer';
import path from 'path';
const app = Express();
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, path.resolve(__dirname, './uploads/'));
},
filename: (req, file, callback) => {
callback(null, file.originalname);
},
});
const upload = multer({ storage });
app.post('/api/upload', upload.single('file'), (req, res) => {
res.send('upload succeeded.');
});
JSでファイル送信する場合
FormDataを使うとform送信と同じmultipart/form-data送信になる。
これにappendでFileインスタンスを渡せばJS側で送信できる。
code:typescript
async function uploadImageFile(imageFile: File) {
const formData = new FormData();
formData.append('file', imageFile);
await axios.request({
method: 'post',
url: '/api/upload',
data: formData,
});
}
補足: base64にして送る方法
ファイルデータを送るためにはmultipart/form-dataにする必要があるが、base64に変換して文字列にした場合はJSON形式でのリクエストは可能。ただし、base64は通常の33%データ量増やすためオススメできない。データ量が多いとそもそも容量制限に引っかかってアップロードできない場合もあるため、基本的にはバイナリでアップロードするのがベター。
canvasからの場合だとやりがちだが、ちゃんとFormDataで送った方が無難。
Fileデータ取得の色々
Fileに変換さえできればFormDataとして送信できるので、Fileとして取得する方法を記載する
input(type="file")から取得する方法
onChangeの時にfilesから取得できる
code:typescript
elInput.addEventListener('change', (event: Event) => {
const elInput = event.currentTarget as HTMLInputElement;
const file = elInput.files0; });
ファイルドロップから取得する方法
dataTransferにfilesが入るため、そこから取得する
code:typescript
element.addEventListener('drop', (event: DragEvent) => {
if (event.dataTransfer == null) {
return;
}
const file = event.dataTransfer.files0; });
canvasデータをFileに変換する
toBlobでblobを取得し、そこからFileに変換する。
code:typescript
function getBlob(elCanvas: HTMLCanvasElement) {
return new Promise<Blob>((resolve, reject) => {
elCanvas.toBlob((blob) => {
if (blob == null) {
reject();
return;
}
resolve(blob);
});
});
}
(async () = {
const blob = await getBlob(document.getElementById('canvas') as HTMLCanvasElement));
const file = new File(blob, 'file.png', { type: blob.type }); })();
toBlobは未サポートのブラウザもあるが、そこはポリフィルを入れた方が分かりやすい。
(結局toDataURLでbase64で取得したものをblobに変換するので)
ネットにある画像データをFileに変換する
バイナリで画像を取得して、そこからバイナリ → Blob → Fileと変換する。
code:typescript
// バイナリで取得する
const response = await axios.request({
method: 'get',
responseType: 'arraybuffer',
url: 'image url',
});
// レスポンスヘッダーからimage/pngなどのファイル形式を受け取れるので取得する
// バイナリ -> Blob -> Fileと変換
const file = new File(blob, 'copy filename', { type: fileType }); 補足: プレビュー機能について
プレビューをする場合はFileReaderを使ってFileデータを読み取る。よくFileの取得と一緒に書かれている記事を見るが、分けていて問題ない。
code:typescript
function getPreviewUrl(file: File) {
return new Promise<string>((resolve) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result as string);
};
reader.readAsDataURL(file);
});
}
その他参考記事
色々な変換を知れる
バイナリから画像形式を取得する
メモ
Base64 = 2**6
Binary = Byte = 8 bits = 2**8
8/6= 1.33
なのでBinary表現の方がBase64表現よりも3/4の長さになる(らしいです)
質疑
ファイルのドラッグアンドドロップも、input type=file も、なんかShift押しながらだと複数いけませんでしたっけ・・・?
input要素は、multipleをつけると複数ファイルを受け取れる。