この記事はJavaScript Advent Calendar 2020の25日目になります。
レビューサイトを始めとする「ユーザーが画像ファイルをそれぞれがアップロードして投稿するウェブサイト」を作るとき、必ず用いられるのが<input type="file">
によるアップロードです。
一般的にファイルのアップロードといえば、PHPやSQLといったバックエンド技術が主役のように捉えられがちです。
しかし、取得した画像ファイルを用いたサムネイルの生成や画像ファイルの軽量化など、JavaScriptの出番も少なくありません。
実際に<input type="file">
を取り扱い、いくつかハマりやすいと感じたポイントがあったため、この場を借りてまとめていきます。
<input type="file">
のvalue
は、画像データではない
1. <input>
要素は、入力されたテキストをステータスをvalue
の中に格納し、保持します。
<input type="file">
を例外ではなく、value
で値を受け取り、value
を空にすることでデータを削除することができます。
しかし、そのvalue
内部をconsole.log
で確認すると、値は画像データではありませんでした。
C:\fakepath\image.jpg
<input type="file">
のvalue
に保存されるのは、画像のデータではなくパスです。
取得した画像データはvalue
ではなく、files
の中に配列で格納されます。
画像のプレビューなど、取得した画像をページ上に再描写させる機能を実装する際には、value
ではなくfiles
のデータを用いることになります。
また、<input type="file">
のvalue
にはアクセス制約あるため、取得した画像ファイルのフルパスは取得できません。
各ブラウザによって取得されるパスの形式は異なりますが、大体の場合「C:/fakepath/」などのダミーパスが表示されます。
もし、ファイル名をページ上に表示させる画面を作りたいのであれば、ファイル名の取得には、value
ではなくfiles[i].name
を用いたほうがよいでしょう。
同じくセキュリティの観点から、value
そのものを修正することも禁止されています。
<input type="file">
で取得した画像データは、置き換えできない
2. files
にはFile
コンストラクタがFileList
という名の配列になって格納されています。
FileList {0: File, length: 1}
0: File
lastModified: 1607151722901
lastModifiedDate: Sat Dec 05 2020 16:02:02 GMT+0900 (日本標準時) {}
name: "image.jpg"
size: 145455
type: "image/jpeg"
webkitRelativePath: ""
length: 1
File
は、new File()
とすることで生成できるため、一見上書きが可能なように見えます。
しかし、実際にfiles
を故意にFile
で上書きしようとすると失敗します。
Uncaught TypeError: Failed to set an indexed property on 'FileList': Index property setter is not supported.
これも、<input type="file">
のセキュリティが原因です。
画像圧縮などでどうしてもデータを更新したいときには、画像をbase64に変換し、<input type="hidden">
など別のパーツにデータを保持させれば解決できます。
<input type="file">
に残っている元の画像データは、送信されないように削除しておくことを忘れずに。
また、データが挿入できるということは、改竄もできるということです。
value
の中身が改竄されたり、意図していないデータが送信されるリスクも存在します。
そのため、バックエンド側でのバリデーションや送信できるデータのサイズ制限は必須となります。
まとめ
個人的に、フォーム関連パーツのHTMLとJavaScriptはなかなか融通が聞かない印象です。
難問にぶつかったとき、それを無理やり解決するのではなく、仕様をしっかり読み解いていくことを意識していきたいですね。