はじめに
Webアプリケーションを作る際に、投稿機能に画像アップロード機能をつけました。その際、画像をアップロードしても投稿ボタンを押すまで画像がちゃんと貼られているのかわからない事態が起こりました。なので、画像をアップロードした際に即時的に画像のプレビューを表示することでその問題を解決することにしました。
完成のコード
まずは完成したコードの全体感を載せます。後にコードの解説をいたします。
<%= form_with model: @post do |form| %>
<div data-controller="image-preview">
<!-- 画像選択 -->
<%= form.file_field :image,
data: {
image_preview_target: "input",
action: "change->image-preview#preview"
} %>
<!-- プレビュー -->
<img
data-image-preview-target="preview"
hidden
width="200">
<!-- 削除ボタン -->
<button
type="button"
data-image-preview-target="removeButton"
data-action="click->image-preview#remove"
hidden>
画像削除
</button>
</div>
<%= form.submit "投稿" %>
<% end %>
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input", "preview", "removeButton"]
preview() {
const file = this.inputTarget.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = (e) => {
this.previewTarget.src = e.target.result
this.previewTarget.hidden = false
this.removeButtonTarget.hidden = false
}
reader.readAsDataURL(file)
}
remove() {
this.inputTarget.value = ""
this.previewTarget.src = ""
this.previewTarget.hidden = true
this.removeButtonTarget.hidden = true
}
}
解説
まずは、ビューの要素とStimulusコントローラーを繋げる必要があります。
<div data-controller="image-preview">
この記述で今回使用する画像投稿時のプレビューを表示するコントローラー(image-preview_controller.js)と繋げることができます。
Targetの指定
JSの方で
static targets = ["input", "preview", "removeButton"]
と記述がありますが、こちらは実際の画像投稿フォームのHTML要素を指定することになります。
紛らわしいかもしれませんが、StimulusではHTML側からJSのどの要素に対応するか記述することになります。
実際に、
<img
data-image-preview-target="preview"
hidden
width="200">
画像を表示させる要素からdata-image-preview-target="preview"といったようにJSのターゲットと紐づけています。
画像のプレビュー表示
const file = this.inputTarget.files[0]
ここで画像アップロードフォームから画像をfileという定数に格納します。[0]としているので画像ファイルは一枚を想定しております。
画像が存在する場合、
reader.onload = (e) => {
this.previewTarget.src = e.target.result
this.previewTarget.hidden = false
this.removeButtonTarget.hidden = false
}
といった処理に移行します。
reader.onloadによって画像の読み込みが正常に終わった後に処理が行われます。
今イベントオブジェクトには画像の情報があります。
例えば、
this.previewTarget.src = e.target.result
この記述で、HTMLのpreview要素にsrc = 画像のURLが追加されるということになります。
同様に、preview要素とremoveButtonのhidden要素をfalseとするため、画像がアップロードされた場合、画像のプレビューと削除ボタンが表示されるということになります。
reader.readAsDataURL(file)
こちらで添付画像のurlが作られ、render.onloadに結果が返されます。
プレビュー画像削除
プレビュー画像が表示されている間、削除ボタンのhiddenがfalseになっているため、表示されクリックすることができるようになっています。
data-action="click->image-preview#remove"
これによってプレビュー画像を削除できるようになっております。
data-action属性でイベント→コントローラ名#メソッドを指定するとStimulusコントローラのメソッドを実行することができます。
では実際にStimulusコントローラのremoveメソッドを見てみると
remove() {
this.inputTarget.value = ""
this.previewTarget.src = ""
this.previewTarget.hidden = true
this.removeButtonTarget.hidden = true
}
""を代入することで、すでに添付されていた画像の情報を消し、hiddenをtrueにすることでプレビュー部分とボタンの部分を非表示にしています。
まとめ
- data-controllerでhtmlの要素とコントローラを接続する
- イベント→コントローラ名#メソッドでコントローラのメソッドを動かす
- FileReader を使うことでクライアント側で画像プレビューが可能
画像がアップロードされているか確認できるシンプルなフロント側の制御ができます。
プレビュー機能以外でもStimulusはシンプルなフロント制御ができるので、様々なことに応用していこうと思います。