運営者:そうすけ
愛媛在住のエンジニア兼ブロガー。
工場勤務から社内SEにキャリアチェンジ。
主に社内向けのシステム開発を行っています。
【ブログ運営歴】2021.6~
【プログラミング歴】2022.3~
本業:Java,MySQL、個人:Javascript
初心者がNuxt(vue)とRailsでActiveStorageを使って画像アップロードしようと思ったときに詰まったことがかなりありそうだったので、備忘録として記事にしました。
計9個あります。。笑
今回は前編として、後編をまた出します。
全体の流れ
- フロントエンドで画像ファイルをアップロード
- アップロードしたタイミングでプレビュー表示
- アップロードした写真をPostメソッドで送信
- Rails側でActiveRecordを使ってDBに保存
- 登録後の画像をURLをRails側から取得して表示
ハマったことリスト
フロントエンド(Nuxt側)
- Fileクラスでブロブデータとして扱わないといけない
- input type=file の特性を知らないといけない
- 画像は単なるJSON形式で送信できない
- $fetchを使うときは通常必要なヘッダーオプションがいらない
- 画像プレビュー時はFileReaderというAPIが必要
- フロント側で画像圧縮が必要
バックエンド(Rails側)
- ストロングパラメーター
- 複数画像の表示URLの取得はActiveStorage::Attached::Manyであることに注意しないといけない
- 複数画像の更新と削除は画像オブジェクトの配列で決まる
Nuxt3側でハマったこと
- Fileクラスでブロブデータとして扱わないといけない
- input type=file の特性を知らないといけない
- 画像は単なるJSON形式で送信できない
- $fetchを使うときは通常必要なヘッダーオプションがいらない
- 画像プレビュー時はFileReaderというAPIが必要
- フロント側で画像圧縮が必要
画像はblobオブジェクトとして扱わないといけない
Javascriptで画像を扱うときの基礎的な知識ですが、画像はblob(ブロブ)というオブジェクトで扱います。
blobとはバイナリーデータと言って、画像ファイルを0と1だけで表現したデータのことです。
通常の文字列や数値の演算とは違って、Blobオブジェクトとしての扱い方が必要になります。
大前提の知識にはなりますが、私は恥ずかしながら知りませんでした。
また基本的に端末の画像はファイルとなっているので、Fileオブジェクトで扱います。
Fileオブジェクトは、Blobオブジェクトに、ファイル名や作成日時などのデータを持ったオブジェクトになります。
下記のような、ファイルをアップする表示は、クライアント端末とFileオブジェクトのやり取りをしています。
画像自体はblobオブジェクトでできており、すべてのファイルはFileオブジェクトで扱われる。
input type=file の特性を知らないといけない
HTMLで画像をアップロードするには、inputタグのtype=fileを使用するのが一般的です。
下記のようなinputタグを書いてみます。
<input type="file"
id="image"
accept=" .png, .jpg, .jpeg"
multiple="true"
class="hidden"
@change="handleImageUploaded"/>
すると、下記のようなHTMLが出てきます。
このinputタグはクリックすると、端末のファイルシステム(Windowsならエクスプローラー)を立ち上げてくれます。
そしてクリックしてファイルを選ぶと、選択したFileオブジェクトを格納してくれます。
属性の意味は以下の通りです。
属性 | 説明 |
type | fileにすると、ファイルシステムからアップロードできる |
accept | 指定した拡張子のみ許容 |
multiple | 複数ファイル選択を許容 |
id | JS用のID |
class | CSS用のクラス |
@change | イベント発火用(Vue用) |
イベントの発火を知る
ファイルアップロードが完了すると、@changeイベントが発火されます。
inputタグがfileオブジェクトのデータを保有します。
ただこのinputタグは一時的にしかデータを保持しないので、そのタイミングで変数に保存してあげます。
const fileList = ref<FileList>();
const handleImageUploaded = (e: Event) => {
if (e.target instanceof HTMLInputElement && e.target.files) {
fileList.value = e.target.files
//ここに圧縮処理やバリデーションを記載
}
}
}
装飾はlabelタグで行う
inputタグtype=fileはスタイルの変更が難しく、labelタグでスタイルの変更を行います。
inputタグtype=fileはデフォルトでスタイルがHTMLの規格で組み入れられているので、スタイルの変更の難易度が高いです。
なので、私はlabelタグで挟んで装飾する方法をとっています。
<label for="image" class="text-center">
<font-awesome-icon :icon="['fas', 'camera-retro']" class="text-2xl"/>
<input
type="file"
id="image"
name=""
accept=" .png, .jpg, .jpeg"
multiple="true"
class="hidden"
@change="handleImageUploaded"
/>
</label>
こんな感じで、デフォルトのインプットタグからオシャレなインプットタグに変更できます。
上記の方法は実はあまり推奨されていない方法でもあるので、labelタグにクリックイベントを作成し、Fileイベントをクリックすうるようにする方法のほうがいいかもしれません
バイナリーデータなのでAPIの送信はJSONではなく、FormDataで扱う
フロントからバックにdataを送付するときの形ですが、json形式では送付できません。
なぜなら画像はバイナリーオブジェクトのため、JSON形式のように文字や数字で表現することができないからです。
Content-TypeというHTTP通信のオプション情報があって、この値によって、POSTのBody部分がどのようなデータ形式なのか指定してやることで、リクエスト時もレスポンス時も端末がデータ形式を認識することができます。
例えば、送信される値が画像などのバイナリーファイルなのか、JSONなどのテキストデータなのかという情報をクライアント端末やサーバー側は判断できます。
その方のデータ形式のひとつにFormDataがあります。これはバイナリーデータを送るのに適したデータ形式になっております。
FormDataのリクエストの場合、ヘッダデータは’content-type’: ‘multipart/form-data’になります。
逆にJsonだと画像はそのままだたバイナリーデータと判断できず、おくることができません。
画像ファイルはFormDataというオブジェクト形式でデータを送信することができます。
const formData = new FormData();
for(let i=0; i < files.value.length; i++){
formData.append("images[]",files.value[i])
}
Fetchを使うときは通常必要なヘッダーオプションがいらない
しかしここでトラップがあります。
リクエストに$fetchを使う場合、 {‘content-type’: ‘multipart/form-data’}を使用します。
Nuxtで調べると、通常はよくある記事はaxiosが多く、リクエストヘッダーオプションに {‘content-type’: ‘multipart/form-data’}を付ける必要がある と情報が出てきます。
けどvue3 nuxt3はfetchを多く使うのですが、これにはこのヘッダーを付けると、Rails側で変換エラーが起こってしまいます。
{‘content-type’: ‘multipart/form-data’}を明示的に指定してしまうと、Bodyをくぎるために自動でできるデータが {‘content-type’: ‘multipart/form-data’}で上書きされてしまい、サーバー側がリクエスト文をうまく読み込めないようです。
この記事がめちゃくちゃわかりやすいです
コメント