fukke.cafe

JS 画像のアップロード、プレビュー機能を実装

HTML部分

<label for="image">アップロードする画像を選択してください。</label>
<input type="file" id="image" accept="image/*" name="image" multiple>
<div class="container"></div>
inputのtypeをfileにするとファイルをアップロードできるようになります。
そしてacceptでアップロードできるファイルを絞っています。今回は画像のみにしたいのでimage/*にしました。 jpg, png, gifとかをアップロードできます。
multiple属性は複数アップロードできるようにするためです。
containerの中に画像のプレビューをしていきます。

単純な実装

1枚の画像のみの場合は、こんな感じでできます。
element.addEventListener("input", (event) => {
  const target = event.target;
  const files = target.files;
  const file = files[0];

  const reader = new FileReader();
  reader.onload = () => {
    const img = new Image();
    img.src = reader.result;
    container.appendChild(img);
  };
  reader.readAsDataURL(file);
});
filesのなかにアップロードした画像があります。
FileReaderは画像をプレビューするためにDataURLとして読み取ってくれます。(非同期)
FileReader.onloadで読み取りが終わったら画像を表示させてます。
この実装でも良いんですけどちょっと単調なので、
  1. FileReaderで読み取るところを関数化したい
  2. 複数枚に対応させたい
です。
なのでもう少しいじっていきます.

Readerを関数化

こちらの通りです
参考
https://blog.shovonhasan.com/using-promises-with-filereader/
const readFileAsDataURL = (file) => {
  const reader = new FileReader();

  return new Promise((resolve, reject) => {
    reader.onerror = () => {
      reader.abort();
      reject(new DOMException("Problem parsing input file."));
    };

    reader.onload = () => {
      resolve(reader.result);
    };

    reader.readAsDataURL(file);
  });
};
Promiseにすることで関数化とawaitが使えるようになってとても嬉しいですね。
エラーが起きたときは読み取りを中断してreject, 読み取りが完了したら結果をresolveしています。

複数枚アップロードとエラーの対処など

fileListは配列のように見せかけてオブジェクトだったんですよねぇ。。
なのでArray.fromメソッドでくるくるしながらさっきの関数に通してプロミスの配列を作っていきます。
そしてPromise.allで一気に実行!
もしrejectされてるやつがあったらエラー起きちゃうのでcatchでの補足とfilterでエラーが起きたやつを取り除いています。
最後にプレビューしていきます。
element.addEventListener("input", async (event) => {
  const target = event.target;
  const files = target.files;

  const arrPromise = Array.from(
    files,
    (file) => readFileAsDataURL(file).catch((e) => e),
  );
  const results = await Promise.all(arrPromise);
  // Errorを弾く
  const validResults = results.filter((result) => !(result instanceof Error));

  for (const result of validResults) {
    const img = new Image();
    img.src = result;
    container.appendChild(img);
  }
});

コード全体

<main>
	<style>
		.container {
			display: flex;
			flex-wrap: wrap;
		}
	</style>
	<label for="image">アップロードする画像を選択してください。</label>
	<input type="file" id="image" accept="image/*" name="image" multiple>

	<div class="container"></div>
</main>
<script>
	const element = document.querySelector('#image')
	const container = document.querySelector('.container')

	const readFileAsDataURL = (file) => {
		const reader = new FileReader()

		return new Promise((resolve, reject) => {
			reader.onerror = () => {
				reader.abort()
				reject(new DOMException('Problem parsing input file.'))
			}

			reader.onload = () => {
				resolve(reader.result)
			}

			reader.readAsDataURL(file)
		})
	}

	element.addEventListener('input', async (event) => {
		const target = event.target
		const files = target.files

		const arrPromise = Array.from(files, file => readFileAsDataURL(file).catch(e => e))
		const results = await Promise.all(arrPromise)
		// Errorを弾く
		const validResults = results.filter(result => !(result instanceof Error))

		for (const result of validResults) {
			const img = new Image()
			img.src = result
			container.appendChild(img)
		}
	})

</script>

実行結果

'タイトル'

formDataでアップロード

こんな感じ
const form = new FormData()
form.append('name', name)
images.forEach(image => {
  if (image.file) form.append(image.key, image.file)
})

base64でアップロード

先頭部分の不要な文字列を削除する
fileData = v.replace(/^data:\w+\/\w+;base64,/, '')

TypeScriptでかくなら

private readFileAsDataURL (file: Blob): Promise<string | ArrayBuffer> {
  const reader = new FileReader()

  return new Promise((resolve, reject) => {
    reader.onerror = () => {
      reader.abort()
      reject(new DOMException('Problem parsing input file.'))
    }

    reader.onload = () => {
      resolve(reader.result)
    }

    reader.readAsDataURL(file)
  })
}

公開日 2020/06/01

目次

Thanks you for reading.